Реагировать на использование асинхронного извлечения данных Reducer - PullRequest
0 голосов
/ 05 ноября 2018

Я пытаюсь получить некоторые данные с помощью нового реагирующего useReducer API и застрял на этапе, когда мне нужно получить его асинхронно. Я просто не знаю как: /

Как разместить выборку данных в операторе switch или это не так, как это должно быть сделано?

import React from 'react'

const ProfileContext = React.createContext()

const initialState = {
  data: false
}

let reducer = async (state, action) => {
  switch (action.type) {
    case 'unload':
      return initialState
    case 'reload':
      return { data: reloadProfile() } //how to do it???
  }
}


const reloadProfile = async () => {
  try {
    let profileData = await fetch('/profile')
    profileData = await profileData.json()

    return profileData
  } catch (error) {
    console.log(error)
  }
}

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState)

  return (
    <ProfileContext.Provider value={{ profile, profileR }}>
      {props.children}
    </ProfileContext.Provider>
  )
}

export { ProfileContext, ProfileContextProvider }

Я пытался сделать это так, но он не работает с асинхронным; (

let reducer = async (state, action) => {
  switch (action.type) {
    case 'unload':
      return initialState
    case 'reload': {
      return await { data: 2 }
    }
  }
}

Ответы [ 3 ]

0 голосов
/ 12 марта 2019

Я написал очень подробное объяснение проблемы и возможные решения. Дан Абрамов предложил решение 3.

Примечание. В примерах приведены примеры операций с файлами, но для извлечения данных может быть реализован тот же подход.

https://gist.github.com/astoilkov/013c513e33fe95fa8846348038d8fe42

0 голосов
/ 18 марта 2019

Я обернул метод отправки слоем, чтобы решить проблему асинхронного действия.

Вот начальное состояние. Клавиша loading записывает текущее состояние загрузки приложения. Это удобно, когда вы хотите показать страницу загрузки, когда приложение извлекает данные с сервера.

{
  value: 0,
  loading: false
}

Существует четыре вида действий.

function reducer(state, action) {
  switch (action.type) {
    case "click_async":
    case "click_sync":
      return { ...state, value: action.payload };
    case "loading_start":
      return { ...state, loading: true };
    case "loading_end":
      return { ...state, loading: false };
    default:
      throw new Error();
  }
}
function isPromise(obj) {
  return (
    !!obj &&
    (typeof obj === "object" || typeof obj === "function") &&
    typeof obj.then === "function"
  );
}

function wrapperDispatch(dispatch) {
  return function(action) {
    if (isPromise(action.payload)) {
      dispatch({ type: "loading_start" });
      action.payload.then(v => {
        dispatch({ type: action.type, payload: v });
        dispatch({ type: "loading_end" });
      });
    } else {
      dispatch(action);
    }
  };
}

Предположим, существует асинхронный метод

async function asyncFetch(p) {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(p);
    }, 1000);
  });
}

wrapperDispatch(dispatch)({
  type: "click_async",
  payload: asyncFetch(new Date().getTime())
});

Полный пример кода здесь:

https://codesandbox.io/s/13qnv8ml7q

0 голосов
/ 05 ноября 2018

Это интересный случай, который примеры useReducer не затрагивают. Я не думаю, что редуктор является подходящим местом для асинхронной загрузки. Исходя из мышления Redux, вы, как правило, загружаете данные в другом месте, либо в виде thunk, наблюдаемой (например, redux-observable), либо просто в событии жизненного цикла, таком как componentDidMount. С новым useReducer мы могли бы использовать componentDidMount подход с использованием useEffect. Ваш эффект может быть примерно таким:

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState);

  useEffect(() => {
    reloadProfile().then((profileData) => {
      profileR({
        type: "profileReady",
        payload: profileData
      });
    });
  }, []); // The empty array causes this effect to only run on mount

  return (
    <ProfileContext.Provider value={{ profile, profileR }}>
      {props.children}
    </ProfileContext.Provider>
  );
}

Также, рабочий пример здесь: https://codesandbox.io/s/r4ml2x864m.

Если вам нужно передать реквизит или состояние вашей функции reloadProfile, вы можете сделать это, настроив второй аргумент на useEffect (пустой массив в примере), чтобы он выполнялся только при необходимости. Вам нужно будет либо сравнить с предыдущим значением, либо внедрить какой-либо кэш, чтобы избежать необходимости извлекать его, когда в этом нет необходимости.

Обновление - перезагрузка от ребенка

Если вы хотите иметь возможность перезагрузить дочерний компонент, вы можете сделать это несколькими способами. Первый вариант - передача обратного вызова дочернему компоненту, который инициирует отправку. Это может быть сделано с помощью провайдера контекста или компонента prop. Поскольку вы уже используете провайдер контекста, вот пример этого метода:

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState);

  const onReloadNeeded = useCallback(async () => {
    const profileData = await reloadProfile();
    profileR({
      type: "profileReady",
      payload: profileData
    });
  }, []); // The empty array causes this callback to only be created once per component instance

  useEffect(() => {
    onReloadNeeded();
  }, []); // The empty array causes this effect to only run on mount

  return (
    <ProfileContext.Provider value={{ onReloadNeeded, profile }}>
      {props.children}
    </ProfileContext.Provider>
  );
}

Если вы действительно хотите использовать функцию диспетчеризации вместо явного обратного вызова, вы можете сделать это, поместив диспетчер в функцию более высокого порядка, которая обрабатывает специальные действия, которые обрабатывались бы промежуточным ПО в мир Redux. Вот пример этого. Обратите внимание, что вместо передачи profileR непосредственно в провайдер контекста, мы передаем пользовательский, который действует как промежуточное ПО, перехватывая специальные действия, которые не имеют значения для редуктора.

function ProfileContextProvider(props) {
  let [profile, profileR] = React.useReducer(reducer, initialState);

  const customDispatch= useCallback(async (action) => {
    switch (action.type) {
      case "reload": {
        const profileData = await reloadProfile();
        profileR({
          type: "profileReady",
          payload: profileData
        });
        break;
      }
      default:
        // Not a special case, dispatch the action
        profileR(action);
    }
  }, []); // The empty array causes this callback to only be created once per component instance

  return (
    <ProfileContext.Provider value={{ profile, profileR: customDispatch }}>
      {props.children}
    </ProfileContext.Provider>
  );
}
...