Async/Await 似乎在 class 方法和函数表达式中有不同的行为(React Hooks,Redux-Thunk)

Async/Await seems to have different behaviour in class methods and function expressions (React Hooks, Redux-Thunk)

我正在将基于 class 的 React 系统迁移到 hooks,我面临着一些我无法理解的挑战。

看看下面的代码片段:

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

在这个方法中,我正在调度一个操作来更新 redux 状态,在此之后我正在记录结果以确保信息在 redux 状态下被获取和更新。

问题是在使用功能组件的应用程序中,结果似乎不一样。它没有更新 redux 状态并在之后立即恢复信息,它似乎根本没有更新状态,即使我使用 "await" 和 class 组件完全相同的动作和缩减器使用:

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

我对两个组件(class 组件和功能组件)的连接:

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

更新对象函数:

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)
  }

我通常在功能组件中实现 thunk,例如:

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

你能试试这个并回复吗

最初传递给渲染函数的props对象不会被改变;而是在其 next 渲染中传递给组件的道具将被更新。这更符合通量架构。你触发并忘记一个动作,reducer 运行,然后你的组件用新的 props 重新渲染。

之前,同样的事情发生了,但是新的道具又被分配给了this.props。由于 "this" 不再有意义,因此您不能使用此模式。此外,依赖这种行为并不是 React 惯用的做事方式。

更新:

我认为这就像我遇到过的大量案例一样,在这些案例中,React 团队似乎对很多处理派生状态的人处理不当的用例矫枉过正(参见 You Probably Don't Need Derived State)。我见过很多案例,比如你的案例,现在已弃用的 componentWillReceiveProps 生命周期方法很好地解决了基于 class 的组件的这个问题。

值得庆幸的是,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 值自上次渲染后发生变化时才会启动。您可以对产品类型执行类似的效果。

另一个可能不太优雅的选项是启动 redux 操作的异步函数也解析为一个值,然后您可以在状态设置代码中使用该值:

const operatingSystems = await props.onFetchOperatingSystems();
// ...now set your state