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



我正在将一个基于类的react系统迁移到钩子,我面临着一些我无法理解的挑战。

看看下面的片段:

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

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

问题是,在使用功能组件的应用程序中,结果似乎不一样。它没有更新redux状态并立即恢复信息,而是似乎根本没有更新状态,即使我使用的是"等待"以及类组件使用的相同操作和reducers:

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

最初传递给渲染函数的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值自上次渲染以来发生更改时,才会启动效果代码。您可以对产品类型执行类似的效果。

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

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

i通常在功能组件中实现thunks,如:

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

你能试试这个并回复评论吗。

最新更新