繁体   English   中英

useReducer 和 useEffect 钩子来获取数据

[英]useReducer and useEffect hooks to fetch data

我正在试验useEffect钩子,但我对如何使用useEffectuseReducer感到有些困惑。

我有一个带有几个复选框的简单页面,在更改每个复选框时,我应该重新查询 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,但也没有运气。

我的组件中有更多不相关的内容,因此我尝试仅将与特定任务相关的代码放在这里。 我希望我清楚地解释了自己

您没有正确更新状态。

状态更新时未触发useEffect原因是因为您正在直接改变状态 您正在从减速器返回相同的状态对象。 因此,就 React 而言,reducer 函数在调用时不会更新状态。 因此,不会再次触发useEffect

您需要从 reducer 返回一个新的 state 对象,以便 React 知道该 state 已被 reducer 更新。 修改您的减速器以在每次更新状态时返回一个新对象。

另一种解决方案可能是使用 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))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM