我正在尝试使用react挂钩,但是我对如何使用useEffect
和useReducer
感到有些困惑。
我有一个包含几个复选框的简单页面,在更改每个复选框时,我应该重新查询api以加载新数据。
我的想法是对组件进行如下编码:
export const Page: React.FunctionComponent = () => {
// state to manage the list returned by the api
const [list, setList] = useState<any[]>([]);
// this should manage the state related to the checkboxes
const [checkboxesState, dispatch] = useReducer(reducer, initialCheckboxesState);
useEffect(() => {
// load data from api
loadProducts(checkboxesState);
}, [checkboxesState]); // my idea here is that when checkboxesState changes, it should trigger the 'useEffect' hook
}
现在有关useReducer
// initial state
const initialFiltersState: CheckboxesState = {
country: [], // array of strings for countries ie ['USA', 'Brazil', 'India']
};
function reducer(
checkboxesState: CheckboxesState,
action: { type: string; value: string; checked: boolean }
) {
const index = checkboxesState[action.type].indexOf(action.value);
// if checkbox is ticked and it's not already present in the array, insert it
if (action.checked && index === -1) {
checkboxesState[action.type].push(action.value);
} else if (!action.checked && index > -1) {
// if it's present and we are unchecking it, remove it from the array
checkboxesState[action.type].splice(index, 1);
}
// TODO: this is not the best way to handle reload. I don't want to do it here, this should just trigger the useEffect
loadProducts(productFilters);
return checkboxesState;
}
// this is the handler for checkbox changes
const onChangeHandler = (event: any): void => {
dispatch({
type: event.target.name, // eg. country
value: event.target.value, // eg. Australia
checked: event.target.checked,// bool
});
};
现在我知道我可能正在做一些愚蠢的事情,但是我已经坚持了很长时间。知道为什么checkboxesState
更改时未调用useEffect吗?还是我完全偏离了轨道?
我试图通过JSON.stringify(checkboxesState)
来使用useEffect,但也没有运气。
我的组件中有一些不相关的内容,因此我尝试仅在此处放置与特定任务相关的代码。我希望我能清楚地解释自己
答案 0 :(得分:2)
您没有正确更新状态。
之所以在更新状态时未触发useEffect
是因为您直接更改状态。您正在从化简器返回相同的状态对象。因此,就React而言,reduce函数在被调用时不会更新状态。因此useEffect
不会再次触发。
您需要从化简器返回一个新的状态对象,以便React知道该化简器已经更新了状态。修改您的reducer以在每次更新状态时返回一个新对象。
答案 1 :(得分:0)
另一种解决方案是使用 thunk 动作,这些动作总是可以访问最近的状态并且可以分派多个动作。 Thunk 动作甚至可以调用其他 thunk 动作并等待它们(如果 thunk 动作返回承诺):thunkAction(arg)(dispatch,getState).then(thunkActionResult=>otherStuff)
.
Thunk 有很好的文档记录并且很容易被其他开发者识别。
Thunk 是 Redux 的中间件,但可以通过自定义钩子应用于 useReducer:
const { useRef, useState } = React;
//custom hook, you can define in different file
const compose = (...fns) =>
fns.reduce((result, fn) => (...args) =>
fn(result(...args))
);
const mw = () => (next) => (action) => next(action);
const createMiddleware = (...middlewareFunctions) => (
store
) =>
compose(
...middlewareFunctions
.concat(mw)
.reverse()
.map((fn) => fn(store))
);
const useMiddlewareReducer = (
reducer,
initialState,
middleware = () => (b) => (c) => b(c)
) => {
const stateContainer = useRef(initialState);
const [state, setState] = useState(initialState);
const dispatch = (action) => {
const next = (action) => {
stateContainer.current = reducer(
stateContainer.current,
action
);
return setState(stateContainer.current);
};
const store = {
dispatch,
getState: () => stateContainer.current,
};
return middleware(store)(next)(action);
};
return [state, dispatch];
};
//middleware
const thunkMiddleWare = ({ getState, dispatch }) => (
next
) => (action) =>
typeof action === 'function'
? action(dispatch, getState)
: next(action);
const logMiddleware = ({ getState }) => (next) => (
action
) => {
console.log('in log middleware', action, getState());
Promise.resolve().then(() =>
console.log('after action:', action.type, getState())
);
return next(action);
};
//your actions
const init = { value: 'A' };
const TOGGLE = 'TOGGLE';
const later = () =>
new Promise((r) => setTimeout(() => r(), 500));
const otherThunk = () => (dispatch, getState) => {
dispatch({ type: 'not relevant action form otherTunk' });
console.log('in other thunk, state is:', getState());
//note that I am returning a promise
return later();
};
const thunkToggle = () => (dispatch, getState) => {
//dispatching other thunk and waiting for it to finish
otherThunk()(dispatch, getState).then(() =>
dispatch({ type: TOGGLE })
);
};
//your component code
const reducer = (state, { type }) => {
console.log(`in reducer action type: ${type}`);
//toggle state.value between A and B
if (type === TOGGLE) {
return { value: state.value === 'A' ? 'B' : 'A' };
}
return state;
};
const middleware = createMiddleware(
thunkMiddleWare,
logMiddleware
);
const App = () => {
const [state, dispatch] = useMiddlewareReducer(
reducer,
init,
middleware
);
return (
<div>
<button onClick={() => dispatch(thunkToggle())}>
toggle
</button>
<pre>{JSON.stringify(state, undefined, 2)}</pre>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
使用 thunk,您可以将逻辑移出组件,只需执行 dispatch(fetchData(args))