英文:
Make clicking a DOM <input> ref React.StrictMode compliant
问题
以下是翻译好的部分:
"The component below is used to trigger opening a file input dialogue in desktop browsers and opening the camera on mobile devices. It is attached high in the component tree and is thus mounted once at startup, so the input
DOM element is (in production) permanent. In React.StrictMode
the component will trigger opening the file dialogue twice. This is fine - I know what strict mode is, but I am unsure how I can change the component to avoid triggering the input twice. We click
the file input in the shouldComponentUpdate
method and I guess there should be some kind of cleanup done to avoid this happening twice. The thing is, I thought we were doing this cleanup already, by triggering closeCamera()
, which is dispatching an action that ends up changing state in our Redux store, so that eventually props.openedBy
is set to null
. This works fine in non-strict mode, so there is something I am missing ..."
以下是示例代码,请不要翻译。
英文:
The component below is used to trigger opening a file input dialogue in desktop browsers and opening the camera on mobile devices. It is attached high in the component tree and is thus mounted once at startup, so the input
DOM element is (in production) permanent. In React.StrictMode
the component will trigger opening the file dialogue twice. This is fine - I know what strict mode is, but I am unsure how I can change the component to avoid triggering the input twice. We click
the file input in the shouldComponentUpdate
method and I guess there should be some kind of cleanup done to avoid this happening twice. The thing is, I thought we were doing this cleanup already, by triggering closeCamera()
, which is dispatching an action that ends up changing state in our Redux store, so that eventually props.openedBy
is set to null
. This works fine in non-strict mode, so there is something I am missing ...
export class CameraTrigger extends React.Component<Props, State> {
static defaultProps = {
openedBy: undefined,
}
fileInputRef: React.Ref<any>
constructor(props: Props) {
super(props)
this.fileInputRef = React.createRef()
this.state = {
lastOpenBy: undefined,
}
}
/**
* Redux camera.openedBy state is changed to null immediately after displaying file selection window.
* Thanks to that we are sure that we have clear state regardless of the canceling or actual image selection.
* But we need to keep the information about opener to pass it to the saveImageFileAsObjectURL a short while later.
*/
static getDerivedStateFromProps(props: Props, _state: State) {
if (props.openedBy) {
return {
lastOpenBy: props.openedBy,
}
}
return null
}
/* we never re-render the input button */
shouldComponentUpdate(nextProps: Props, _nextState: State) {
const { openedBy } = nextProps
const ref = this.fileInputRef
if (openedBy && ref) {
;(ref as any).current.click()
this.props.closeCamera()
}
return false
}
clearInputBeforeNextUpload = (input: HTMLInputElement) => {
input.value = ''
}
fileInputChanged = (event: React.ChangeEvent<HTMLInputElement>) => {
const input = event.currentTarget as HTMLInputElement
if (input.files && input.files.length) {
const file = input.files[0]
this.props.handleImageFile(file, this.state.lastOpenBy, this.state.lastEncounterId, this.state.lastConversationId)
this.clearInputBeforeNextUpload(input)
}
}
render() {
return (
<input
name="message-file-upload"
type="file"
accept={MimeTypeMap.JPEG}
capture="environment"
ref={this.fileInputRef}
onChange={this.fileInputChanged}
style={{ display: 'none' }}
/>
)
}
}
type Props = {
handleImageFile: Function
closeCamera: Function
openedBy?: string
}
type State = {
lastOpenBy?: string
}
答案1
得分: 1
我通过检查 React 文档中有关纯函数和非纯函数的部分,找出了问题所在以及原因。
在旧的 React StrictMode 文档中,有一个名为 "检测副作用" 的部分,讨论了渲染阶段与提交阶段,以及在渲染阶段调用的函数应该是“纯”的概念。在这个上下文中,纯度意味着避免副作用:
由于上述方法可能会被多次调用,因此它们不应包含副作用非常重要。忽略这一规则可能导致各种问题,
并列出了以下在渲染阶段调用的函数:
渲染阶段生命周期包括以下类组件方法:
- constructor
- componentWillMount(或 UNSAFE_componentWillMount)
- componentWillReceiveProps(或 UNSAFE_componentWillReceiveProps)
- componentWillUpdate(或 UNSAFE_componentWillUpdate)
- getDerivedStateFromProps
- shouldComponentUpdate
- render
- setState 更新函数(第一个参数)
新的文档还提到:
为了帮助您找到意外的非纯代码,Strict Mode 在开发中会调用一些函数(仅调用那些应该是纯的函数)两次。
在随后的列表中,它包括 shouldComponentUpdate
。
这意味着我在 shouldComponentUpdate
中调用的副作用必须移到在渲染阶段之外调用的另一个函数中。正确的方法之一似乎是 componentDidUpdate
。正如文档所述:
您可以在更新后操作 DOM。这也是进行网络请求的常见地方,只要将当前的 props 与以前的 props 进行比较(例如,如果 props 没有更改,则可能不需要进行网络请求)。通常,您会与 componentDidMount 和 componentWillUnmount 一起使用它:
将点击调用移至 componentDidUpdate
对我解决了问题,也解决了问题。这是差异:
- /* 我们不会重新渲染输入按钮 */
- shouldComponentUpdate(nextProps: Props, _nextState: State) {
- const { openedBy } = nextProps
-
- const ref = this.fileInputRef
- if (openedBy && ref) {
- ;(ref as any).current.click()
+ componentDidUpdate(_prevProps: Props, _nextState: State) {
+ const ref = this.fileInputRef as { current: HTMLInputElement }
+ if (this.props.openedBy && ref) {
+ ref.current.click()
this.props.onTrigger()
}
- return false
}
英文:
I found out what was wrong and why through inspecting the React docs on pure vs impure functions.
In the older React docs on StrictMode, they have a section called "Detecting Side Effects" that talks about the render phase vs the commit phase, and that functions that are called in the render phase should be pure. Purity in this context means avoiding side-effects:
> Because the above methods might be called more than once, it’s important that they do not contain side-effects. Ignoring this rule can lead to a variety of problems,
and it lists the following functions as being called in the render phase
> Render phase lifecycles include the following class component methods:
>
>- constructor
>- componentWillMount (or UNSAFE_componentWillMount)
>- componentWillReceiveProps (or UNSAFE_componentWillReceiveProps)
>- componentWillUpdate (or UNSAFE_componentWillUpdate)
>- getDerivedStateFromProps
>- shouldComponentUpdate
>- render
>- setState updater functions (the first argument)
The (new) docs also say:
> To help you find accidentally impure code, Strict Mode calls some of your functions (only the ones that should be pure) twice in development
In the list that follows, it includes shouldComponentUpdate
.
What this means is that my side-effect above, called in shouldComponentUpdate
must be moved to another function that is called outside of the render phase. The correct (at least one way) method seems to be componentDidUpdate
. As the docs state:
> You can use it to manipulate the DOM after an update. This is also a common place to do network requests as long as you compare the current props to previous props (e.g. a network request may not be necessary if the props have not changed). Typically, you’d use it together with componentDidMount and componentWillUnmount:
Simply moving the click-invoking to componentDidUpdate
fixed the issue for me
and that removed the issue for me. Here's the diff
- /* we never re-render the input button */
- shouldComponentUpdate(nextProps: Props, _nextState: State) {
- const { openedBy } = nextProps
-
- const ref = this.fileInputRef
- if (openedBy && ref) {
- ;(ref as any).current.click()
+ componentDidUpdate(_prevProps: Props, _nextState: State) {
+ const ref = this.fileInputRef as { current: HTMLInputElement }
+ if (this.props.openedBy && ref) {
+ ref.current.click()
this.props.onTrigger()
}
- return false
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论