useState 未按预期运行
useState not behaving as expected
我对 useState 有疑问。 editorDataOpen 在从 openEditorData 设置时正确更新,但在从 closeEditorData 设置时不更新。 console.log('Entering Profile > closeEditorData()') 行没有问题。
我在控制台日志中看到的输出是:
- 渲染
- 渲染
- false(editorDataOpen第一次设置)
- 渲染
- 渲染
- 渲染
- 渲染
- true(通过单击打开编辑器将 editorDataOpen 设置为 true)
- 正在进入配置文件 > closeEditorData()(由不同的点击触发以关闭编辑器,应该将 editorDataOpen 设置为 false,但没有设置)
- 渲染
- 渲染
就是这样,它从不打印最后一个 false,这意味着 editorDataOpen 从未设置?
我今天在这上面花了太多时间,我就是看不出错误在哪里。有人可以帮忙吗?这是有问题的代码:
import React from 'react'
import {withTheme} from 'styled-components'
import {withContext} from '../../context/ContextHOC'
import withLoadingScreen from '../hocs/LoadingScreenHOC'
import {getEditorWidth} from '../../functions/funcEditor'
import {saveImagePlus} from '../../functions/funcDataSaver'
import Submodule from '../ui/Submodule'
import ImageCapsule from '../ui/ImageCapsule'
import EditorImage from '../editors/EditorImage'
import EditorProfileData from '../editors/EditorProfileData'
import Spacer from '../ui/Spacer'
import Table from '../ui/Table'
import ContainerGrid from '../ui/ContainerGrid'
import * as ops from '../../functions/funcStringMath'
import * as parse from '../../functions/funcDataParser'
const Profile = (props) => {
const s = props.theme.sizes
const c = props.theme.cards
const {setLoadingOn, setLoadingOff} = props
const [image, setImage] = React.useState(props.context.current.modules.me.profile.image)
const [editorImageOpen, setEditorImageOpen] = React.useState(false)
const [editorDataOpen, setEditorDataOpen] = React.useState(false)
const openEditorImage = () => setEditorImageOpen(true)
const openEditorData = () => setEditorDataOpen(true)
const closeEditorImage = () => {
setEditorImageOpen(false)
setLoadingOff()
}
const closeEditorData = () => {
console.log('Entering Profile > closeEditorData()')
setEditorDataOpen(false)
setLoadingOff()
}
React.useEffect(() => console.log(editorDataOpen), [editorDataOpen])
const updateAfterSavingImage = (img) => {
setImage({
url: img.url,
scale: img.scale,
position: img.position
})
closeEditorImage()
}
const handleImageChanged = (img) => {
if (img != undefined){
setLoadingOn()
const data = {
companyId: props.context.current.company.id,
userId: props.context.current.user.id,
routeFile: props.context.routes.meProfileImage,
routeData: props.context.routes.meProfileImageData,
}
saveImagePlus(img, data, updateAfterSavingImage)
}
else {
console.log('Error: Image received is undefined, cannot save.')
closeEditorImage()
}
}
const spacer =
<Spacer
width = '100%'
height = {s.spacing.default}
/>
const unparsedData = props.context.current.modules.me.profile.data
const parsedData = parse.profileData(props.context.current.modules.me.profile.data)
console.log('Render')
return(
<Submodule
isMobile = {c.cardsPerRow == 1 ? true : false}
header = {{
text: 'Profile',
}}
{...props}
>
<ImageCapsule
onClick = {openEditorImage}
id = {'container_imageprofile'}
width = '100%'
image = {image}
$nodrag
/>
{editorImageOpen &&
<EditorImage
open = {editorImageOpen}
closeSaving = {handleImageChanged}
closeWithoutSaving = {closeEditorImage}
image = {image}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Image',
}}
/>
}
{spacer}
{spacer}
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
onClick = {openEditorData}
$clickable
>
<Table
$nomouse
width = '100%'
data = {parsedData}
settings = {{
cell: {
padleft: s.spacing.default,
padright: s.spacing.default,
padtop: ops.round((ops.divide([s.spacing.default, 4]))),
padbottom: ops.round((ops.divide([s.spacing.default, 4]))),
},
columns: [
{type: 'defaultRight', width: '30%'},
{type: 'default', width: '70%'},
]
}}
/>
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
</ContainerGrid>
{spacer}
{spacer}
</Submodule>
)
}
export default (
withTheme(
withContext(
withLoadingScreen(
Profile
)
)
)
)
编辑:Dehan de Croos 已经解决了这个问题,非常感谢!
因此,正如 Dehan 在下面提到的,该事件冒泡并触发了 ContainerGrid 中的 openEditorData。整个事情令人困惑,因为 editorImageOpen 工作正常而 editorDataOpen 不工作,并且它们都做同样的事情:打开一个编辑器 window。
当 Dehan 解开谜团后,我意识到两者之间的区别在于 ImageCapsule 内部有一个 ClickLayer 组件,它只是用来捕获点击和回调。
我没有将 ClickLayer 与 ContainerGrid 一起使用,这就是事件能够冒泡的原因。
按照 Dehan 的建议,我通过在 ContainerGrid 中添加一个 ClickLayer 来解决问题,如下所示:
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
// onClick = {openEditorData}
$clickable
>
<ClickLayer
onClick = {openEditorData}
/>
<Table
$nomouse
width = '100%'
data = {parsedData}
settings = {{
cell: {
padleft: s.spacing.default,
padright: s.spacing.default,
padtop: ops.round((ops.divide([s.spacing.default, 4]))),
padbottom: ops.round((ops.divide([s.spacing.default, 4]))),
},
columns: [
{type: 'defaultRight', width: '30%'},
{type: 'default', width: '70%'},
]
}}
/>
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
</ContainerGrid>
最好有一个有效的代码片段来诊断此问题,但事件很有可能会冒泡到组件树并在此处触发 openEditorData
事件。
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
onClick = {openEditorData}
$clickable
>
要快速检查,请将下面显示的组件移到 <ContainerGrid ... />
组件之外。并检查这是否可以解决问题。
To clarify here, The move of code should happen so that <
EditorProfileData ... /> will never appear as a child component of
<ContainerGrid ... />
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
如果现在它工作正常并且您迫切需要维护上述组件hierarchy/structure。你可以打电话
stopPropegation 但您需要随身携带原生 JS 事件。为了解释如何做到这一点,我需要知道 <EditorProfileData ... />
是什么样子的。但是假设 close
道具将 return 原生点击事件作为回调,修复看起来像这样。
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
//close = {closeEditorData}
// If close provides the native event use following
close = { e => {
e.stopPropagation();
closeEditorData();
}}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
如果不是,我们需要找到原始的 onClick 事件并将其传递给该道具回调。
我对 useState 有疑问。 editorDataOpen 在从 openEditorData 设置时正确更新,但在从 closeEditorData 设置时不更新。 console.log('Entering Profile > closeEditorData()') 行没有问题。
我在控制台日志中看到的输出是:
- 渲染
- 渲染
- false(editorDataOpen第一次设置)
- 渲染
- 渲染
- 渲染
- 渲染
- true(通过单击打开编辑器将 editorDataOpen 设置为 true)
- 正在进入配置文件 > closeEditorData()(由不同的点击触发以关闭编辑器,应该将 editorDataOpen 设置为 false,但没有设置)
- 渲染
- 渲染
就是这样,它从不打印最后一个 false,这意味着 editorDataOpen 从未设置?
我今天在这上面花了太多时间,我就是看不出错误在哪里。有人可以帮忙吗?这是有问题的代码:
import React from 'react'
import {withTheme} from 'styled-components'
import {withContext} from '../../context/ContextHOC'
import withLoadingScreen from '../hocs/LoadingScreenHOC'
import {getEditorWidth} from '../../functions/funcEditor'
import {saveImagePlus} from '../../functions/funcDataSaver'
import Submodule from '../ui/Submodule'
import ImageCapsule from '../ui/ImageCapsule'
import EditorImage from '../editors/EditorImage'
import EditorProfileData from '../editors/EditorProfileData'
import Spacer from '../ui/Spacer'
import Table from '../ui/Table'
import ContainerGrid from '../ui/ContainerGrid'
import * as ops from '../../functions/funcStringMath'
import * as parse from '../../functions/funcDataParser'
const Profile = (props) => {
const s = props.theme.sizes
const c = props.theme.cards
const {setLoadingOn, setLoadingOff} = props
const [image, setImage] = React.useState(props.context.current.modules.me.profile.image)
const [editorImageOpen, setEditorImageOpen] = React.useState(false)
const [editorDataOpen, setEditorDataOpen] = React.useState(false)
const openEditorImage = () => setEditorImageOpen(true)
const openEditorData = () => setEditorDataOpen(true)
const closeEditorImage = () => {
setEditorImageOpen(false)
setLoadingOff()
}
const closeEditorData = () => {
console.log('Entering Profile > closeEditorData()')
setEditorDataOpen(false)
setLoadingOff()
}
React.useEffect(() => console.log(editorDataOpen), [editorDataOpen])
const updateAfterSavingImage = (img) => {
setImage({
url: img.url,
scale: img.scale,
position: img.position
})
closeEditorImage()
}
const handleImageChanged = (img) => {
if (img != undefined){
setLoadingOn()
const data = {
companyId: props.context.current.company.id,
userId: props.context.current.user.id,
routeFile: props.context.routes.meProfileImage,
routeData: props.context.routes.meProfileImageData,
}
saveImagePlus(img, data, updateAfterSavingImage)
}
else {
console.log('Error: Image received is undefined, cannot save.')
closeEditorImage()
}
}
const spacer =
<Spacer
width = '100%'
height = {s.spacing.default}
/>
const unparsedData = props.context.current.modules.me.profile.data
const parsedData = parse.profileData(props.context.current.modules.me.profile.data)
console.log('Render')
return(
<Submodule
isMobile = {c.cardsPerRow == 1 ? true : false}
header = {{
text: 'Profile',
}}
{...props}
>
<ImageCapsule
onClick = {openEditorImage}
id = {'container_imageprofile'}
width = '100%'
image = {image}
$nodrag
/>
{editorImageOpen &&
<EditorImage
open = {editorImageOpen}
closeSaving = {handleImageChanged}
closeWithoutSaving = {closeEditorImage}
image = {image}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Image',
}}
/>
}
{spacer}
{spacer}
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
onClick = {openEditorData}
$clickable
>
<Table
$nomouse
width = '100%'
data = {parsedData}
settings = {{
cell: {
padleft: s.spacing.default,
padright: s.spacing.default,
padtop: ops.round((ops.divide([s.spacing.default, 4]))),
padbottom: ops.round((ops.divide([s.spacing.default, 4]))),
},
columns: [
{type: 'defaultRight', width: '30%'},
{type: 'default', width: '70%'},
]
}}
/>
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
</ContainerGrid>
{spacer}
{spacer}
</Submodule>
)
}
export default (
withTheme(
withContext(
withLoadingScreen(
Profile
)
)
)
)
编辑:Dehan de Croos 已经解决了这个问题,非常感谢!
因此,正如 Dehan 在下面提到的,该事件冒泡并触发了 ContainerGrid 中的 openEditorData。整个事情令人困惑,因为 editorImageOpen 工作正常而 editorDataOpen 不工作,并且它们都做同样的事情:打开一个编辑器 window。 当 Dehan 解开谜团后,我意识到两者之间的区别在于 ImageCapsule 内部有一个 ClickLayer 组件,它只是用来捕获点击和回调。 我没有将 ClickLayer 与 ContainerGrid 一起使用,这就是事件能够冒泡的原因。 按照 Dehan 的建议,我通过在 ContainerGrid 中添加一个 ClickLayer 来解决问题,如下所示:
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
// onClick = {openEditorData}
$clickable
>
<ClickLayer
onClick = {openEditorData}
/>
<Table
$nomouse
width = '100%'
data = {parsedData}
settings = {{
cell: {
padleft: s.spacing.default,
padright: s.spacing.default,
padtop: ops.round((ops.divide([s.spacing.default, 4]))),
padbottom: ops.round((ops.divide([s.spacing.default, 4]))),
},
columns: [
{type: 'defaultRight', width: '30%'},
{type: 'default', width: '70%'},
]
}}
/>
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
</ContainerGrid>
最好有一个有效的代码片段来诊断此问题,但事件很有可能会冒泡到组件树并在此处触发 openEditorData
事件。
<ContainerGrid
// bgcolor = '#C43939'
width = '100%'
justify = {s.justify.center}
onClick = {openEditorData}
$clickable
>
要快速检查,请将下面显示的组件移到 <ContainerGrid ... />
组件之外。并检查这是否可以解决问题。
To clarify here, The move of code should happen so that < EditorProfileData ... /> will never appear as a child component of
<ContainerGrid ... />
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
close = {closeEditorData}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
如果现在它工作正常并且您迫切需要维护上述组件hierarchy/structure。你可以打电话
stopPropegation 但您需要随身携带原生 JS 事件。为了解释如何做到这一点,我需要知道 <EditorProfileData ... />
是什么样子的。但是假设 close
道具将 return 原生点击事件作为回调,修复看起来像这样。
{editorDataOpen &&
<EditorProfileData
open = {editorDataOpen}
//close = {closeEditorData}
// If close provides the native event use following
close = { e => {
e.stopPropagation();
closeEditorData();
}}
width = {getEditorWidth(1, c.cardsPerRow, c.card.width, s.spacing.default)}
header = {{
text: 'Edit Profile Data',
}}
data = {unparsedData}
/>
}
如果不是,我们需要找到原始的 onClick 事件并将其传递给该道具回调。