Async / Await, по-видимому, ведет себя по-разному в методах классов и выражениях функций (React Hooks, Redux-Thunk) - PullRequest
1 голос
/ 29 октября 2019

Я перевожу основанную на классе систему реагирования на хуки и сталкиваюсь с некоторыми проблемами, которые не могу понять.

Взгляните на фрагмент ниже:

async onSearchforOptions(elementId) {
    await this.props.onFetchOperatingSystems()
    //(3) [{…}, {…}, {…}]
    console.log(this.props.operatingSystems)
}

В этом методе я отправляю действие для обновления состояния избыточности, и сразу после этого я записываю результат, чтобы убедиться, что информация была получена и обновлена ​​в состоянии избыточности.

Проблемав том, что в приложении, которое использует функциональные компоненты, результат не кажется тем же самым. Вместо того, чтобы обновлять состояние избыточности и восстанавливать информацию сразу после этого, он просто не обновляет состояние, даже если я использую «await» и те же самые действия и редукторы, которые использует компонент класса:

const onSearchforOptions = async (elementId) => {
    await props.onFetchOperatingSystems()
    //[]
    console.log(props.operatingSystems)
}

Мое соединение для обоих компонентов (компонент класса и функциональный компонент):

const mapStateToProps = state => {
  return {
    operatingSystems: state.operatingSystemReducer.operatingSystems
  }
}

const mapDispathToProps = dispatch => {
  return {
    onFetchOperatingSystems: () => dispatch(actions.fetchOperatingSystems())
  }
}

export default connect(mapStateToProps, mapDispathToProps)(productsForm)

Мои действия:

export const fetchOperatingSystemsStart = () => {
    return {
        type: actionTypes.FETCH_OPERATING_SYSTEMS_START
    }
}

export const fetchOperatingSystemsFail = (error) => {
    return {
        type: actionTypes.FETCH_OPERATING_SYSTEMS_FAIL,
        error: error
    }
}

export const fetchOperatingSystemsSuccess = (operatingSystems) => {
    return {
        type: actionTypes.FETCH_OPERATING_SYSTEMS_SUCCESS,
        operatingSystems: operatingSystems
    }
}

export const fetchOperatingSystems = () => {
    return dispatch => {
        dispatch(fetchOperatingSystemsStart())
        return axios.get(url)
            .then(response => {
                const fetchedData = []
                for (let key in response.data) {
                    fetchedData.push({
                        ...response.data[key],
                        id: response.data[key].id
                    })
                }

                dispatch(fetchOperatingSystemsSuccess(fetchedData))
            })
            .catch(error => {
                if (error.response !== undefined) dispatch(fetchOperatingSystemsFail(error.response.data))
                else dispatch(fetchOperatingSystemsFail(error))
            })
    }
}

Мой редуктор:

const initialState = {
    operatingSystems: [],
    loading: false
}

const fetchOperatingSystemsStart = (state) => {
    return updateObject(state, { loading: true })
}

const fetchOperatingSystemsSuccess = (state, action) => {
    return updateObject(state, { operatingSystems: action.operatingSystems, loading: false  })
}

const fetchOperatingSystemsFail = (state) => {
    return updateObject(state, { loading: false })
}

const reducer = (state = initialState, action) => {
    switch (action.type) {
        case actionTypes.FETCH_OPERATING_SYSTEMS_START: return fetchOperatingSystemsStart(state)
        case actionTypes.FETCH_OPERATING_SYSTEMS_SUCCESS: return fetchOperatingSystemsSuccess(state, action)
        case actionTypes.FETCH_OPERATING_SYSTEMS_FAIL: return fetchOperatingSystemsFail(state)
        default: return state
    }
}

export default reducer

функция updateObject:

export const updateObject = (oldObject, updatedProperties) => {
const element =  {
    // The values of the object oldObject are being spread, at the same time the values of
    // updatedProperties are (I'm taking out the attributes of both objects with the spread operator).
    // In this case, since the names of the attributes are the same,
    // the attributes (which were spread) of the first object will have their values replaced
    // by the values of the second object's attributes.
    ...oldObject,
    ...updatedProperties
}

return element

}

Моя цель:

Согласно приведенному ниже фрагменту, моя цель состоит в том, чтобы динамически искать варианты и обновлять их в моемформа, которая находится в состоянии компонента.

  const onSearchforOptions = async (elementId) => {
    let elementUpdated
    switch (elementId) {
      case 'operatingSystem': {

        await props.onFetchOperatingSystems()
        console.log(props.operatingSystems)

        elementUpdated = {
          'operatingSystem': updateObject(productsForm['operatingSystem'], {
            selectValue: {
              value: props.selectedElement.operatingSystem ? props.selectedElement.operatingSystem.id : undefined,
              label: props.selectedElement.operatingSystem ? props.selectedElement.operatingSystem.name : undefined
            },
            elementConfig: updateObject(productsForm['operatingSystem'].elementConfig, {
              options: props.operatingSystems
            })
          })
        }
        break
      }
      case 'productType': {
        await props.onFetchProductTypes()
        elementUpdated = {
          'productType': updateObject(productsForm['productType'], {
            selectValue: {
              value: props.selectedElement.productType ? props.selectedElement.productType.id : undefined,
              label: props.selectedElement.productType ? props.selectedElement.productType.name : undefined
            },
            elementConfig: updateObject(productsForm['productType'].elementConfig, {
              options: props.productTypes
            })
          })
        }
        break
      }
      default: break
    }

    const productsFormUpdated = updateObject(productsForm, elementUpdated)

    setProductsForm(productsFormUpdated)
  }

Ответы [ 2 ]

1 голос
/ 29 октября 2019

Объект props, переданный в функцию рендеринга изначально, не будет видоизменяться;скорее, реквизиты, переданные вашему компоненту при его следующем рендере, будут обновлены. Это больше соответствует архитектуре потока. Вы запускаете действие и забываете действие, редуктор запускается, и затем ваш компонент повторно визуализируется с новыми реквизитами.

Раньше происходило то же самое, но новые реквизиты были назначены на this.propsочередной раз. Поскольку больше нет значимого «этого», вы не можете использовать этот шаблон. Кроме того, в зависимости от этого поведения не существует идиоматического способа действий React.

Обновление :

Я думаю, что это похоже на большое количество случаев, которые я такжевстречались там, где команда React, казалось, слишком корректировала для многих случаев, когда люди плохо обрабатывали производное состояние (см. Возможно, вам не нужно производное состояние ). Я видел множество случаев, подобных вашему, когда устаревший метод жизненного цикла componentWillReceiveProps очень хорошо решил эту проблему для компонентов на основе классов.

К счастью, useEffect теперь дает вам что-то вроде замены,Думайте об этом следующим образом: когда props.operatingSystems изменяется, вы хотите выполнить эффект изменения состояния вашей формы. Это неудачная проблема с двойным обновлением, но у вас это уже было. Вот как вы могли бы написать это:

const [productsForm, setProductsForm] = useState(...);

useEffect(() => {
  // Handle the case where props.operatingSystems isn't initialized?
  if (!props.operatingSystems || !props.selectedElement.operatingSystem) 
    return;

  setProductsForm({
    ...productsForm,
    operatingSystem: {
      ...productsForm.operatingSystem,
      selectValue: {
        value: props.selectedElement.operatingSystem.id,
        label: props.selectedElement.operatingSystem.name
      },
      elementConfig: {
        ...productsForm.operatingSystem.elementConfig,
        options: props.operatingSystems
      }
    }
  });
}, [props.operatingSystems]);

Это работает так, что ваш код эффекта срабатывает только при изменении значения props.operatingSystems с момента последнего рендеринга. Вы можете сделать подобный эффект для типов продуктов.

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

const operatingSystems = await props.onFetchOperatingSystems();
// ...now set your state
0 голосов
/ 29 октября 2019

Я обычно реализую Thunks в функциональном компоненте, например:

`export default connect(mapStateToProps, {fetchOperatingSystems})(productsForm)`

Можете ли вы попробовать это и прокомментировать.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...