useReducer和useEffect挂钩获取数据

时间:2020-08-31 11:35:01

标签: javascript reactjs react-hooks

我正在尝试使用react挂钩,但是我对如何使用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,但也没有运气。

我的组件中有一些不相关的内容,因此我尝试仅在此处放置与特定任务相关的代码。我希望我能清楚地解释自己

2 个答案:

答案 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))