我试图将基于类的组件更改为功能组件,并在尝试读取附加于useEffect()
的处理函数中的状态属性时遇到麻烦。
在基于类的组件内部,我将在componentDidMount
内附加处理程序方法,并且该处理程序可以访问状态。
渲染的输出在两种类型的组件中均显示正确的值!
但是当我需要在处理程序中使用state属性的当前值来计算新的状态值时,我会在读取状态属性时遇到麻烦 内部功能组件。
我创建了以下示例来演示该问题:https://codesandbox.io/s/peaceful-wozniak-l6w2c(打开控制台并滚动)
如果您单击两个组件都可以正常工作:
counter
的当前值counter
如果仅滚动基于类的组件,则可以:
position
的当前值position
但是功能组件的控制台输出仅返回我 初始状态属性值。
基于类的组件
import React, { Component } from "react";
export default class CCTest extends Component {
constructor(props) {
super(props);
this.state = { position: 0, counter: 0 };
this.handleScroll = this.handleScroll.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleScroll(e) {
console.log(
"class.handleScroll()",
this.state.position
);
this.setState({ position: document.body.getBoundingClientRect().top });
}
handleClick() {
console.log("[class.handleClick]", this.state.counter);
this.setState(prevState => ({ counter: prevState.counter + 1 }));
}
componentDidMount() {
window.addEventListener("scroll", this.handleScroll);
}
render() {
return (
<div
style={{
backgroundColor: "orange",
padding: "20px",
cursor: "pointer"
}}
onClick={this.handleClick}
>
<strong>class</strong>
<p>
position: {this.state.position}, counter: {this.state.counter}
</p>
</div>
);
}
}
功能组件
import React from "react";
export default prop => {
const [position, setPosition] = React.useState(0);
const [counter, setCounter] = React.useState(0);
React.useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
};
// eslint-disable-next-line
}, []);
const handleScroll = () => {
console.log(
"function.handleScroll()",
position
);
setPosition(document.body.getBoundingClientRect().top);
};
const handleClick = () => {
console.log("[function.handleClick]", counter);
setCounter(counter + 1);
};
return (
<div
style={{ backgroundColor: "green", padding: "20px", cursor: "pointer" }}
onClick={handleClick}
>
<strong>function</strong>
<p>
position: {position}, counter: {counter}
</p>
</div>
);
};
每个小提示都会帮助<3
答案 0 :(得分:4)
每次渲染组件时,都会创建一个新的handleScroll函数,该函数可以通过组件闭包访问当前状态。但是这种关闭不会在每个渲染器上都得到更新。而是创建了一个新的handleScroll函数,该函数可以查看新值。
问题是,当您这样做时:
window.addEventListener("scroll", handleScroll);
您将函数的 current 版本绑定到事件,并且该事件将始终在那时读取值,而不是新值。通常,useEffect将再次运行,并且将使用该函数的新实例,但是您要使用空数组作为第二个参数来防止它发生。
仅在这些情况下会有棉绒警告,但您已将其禁用:
// eslint-disable-next-line
如果删除此空数组,则可以删除禁用linter规则,它将正常运行。
其他选项
如果每次都不能更改功能(例如,使用非反应库或出于性能原因),则有其他选择:
您可以使用ref代替状态。引用会发生突变,而不是在每个渲染上都创建一个新引用。这样,即使您不更新功能,该功能的先前版本也可以读取并修改当前值。
您可以使用useReducer。调度功能永远不会改变,因此,如果您的useEffect依赖它,它将不会重新运行。 reducer可以访问先前的值和操作,因此即使依赖于先前的值,它也可以计算新状态。
答案 1 :(得分:0)
由于@Anxo的帮助,我最终放弃了useState
并改而使用useRecuder
。
这是我针对最初问题中特定示例的功能组件:
import React from 'react';
const reducer = (state, action) => {
switch (action.type) {
case 'UPDATE_POSITION':
return {
...state,
position: document.body.getBoundingClientRect().top
};
case 'INCREMENT_COUNTER':
return {
...state,
counter: state.counter + 1
};
default:
throw new Error('chalupa batman');
}
};
const initialState = {
position: 0,
counter: 0
};
const FCTest = props => {
const [state, dispatch] = React.useReducer(reducer, initialState);
React.useEffect(() => {
window.addEventListener('scroll', () => {
dispatch({ type: 'UPDATE_POSITION' });
});
return () => {
window.removeEventListener(
'scroll',
dispatch({ type: 'UPDATE_POSITION' })
);
};
// eslint-disable-next-line
}, []);
return (
<div
style={{ backgroundColor: 'green', padding: '20px', cursor: 'pointer' }}
onClick={() => dispatch({ type: 'INCREMENT_COUNTER' })}
>
<strong>function</strong>
<p>
position: {state.position}, counter: {state.counter}
</p>
</div>
);
};
export default FCTest;