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
我正在将基于 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