我正在使用React Context存储数据并提供修改这些数据的功能。
现在,我正在尝试使用React Hooks将类组件转换为功能组件。
虽然一切都可以在Class中正常工作,但我无法在功能组件中正常工作。
由于我的应用程序代码稍微复杂一点,所以我创建了这个小示例(JSFiddle link),该示例可以重现该问题:
首先是上下文,这对于类和功能组件都是相同的:
const MyContext = React.createContext();
class MyContextProvider extends React.Component {
constructor (props) {
super(props);
this.increase = this.increase.bind(this);
this.reset = this.reset.bind(this);
this.state = {
current: 0,
increase: this.increase,
reset: this.reset
}
}
render () {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
);
}
increase (step) {
this.setState((prevState) => ({
current: prevState.current + step
}));
}
reset () {
this.setState({
current: 0
});
}
}
现在,这是Class组件,可以正常工作:
class MyComponent extends React.Component {
constructor (props) {
super(props);
this.increaseByOne = this.increaseByOne.bind(this);
}
componentDidMount () {
setInterval(this.increaseByOne, 1000);
}
render () {
const count = this.context;
return (
<div>{count.current}</div>
);
}
increaseByOne () {
const count = this.context;
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}
}
MyComponent.contextType = MyContext;
预期结果是,以一秒的间隔计数到5,然后从0重新开始。
这是转换后的功能组件:
const MyComponent = (props) => {
const count = React.useContext(MyContext);
const increaseByOne = React.useCallback(() => {
console.log(count.current);
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}, []);
React.useEffect(() => {
setInterval(increaseByOne, 1000);
}, [increaseByOne]);
return (
<div>{count.current}</div>
);
}
与其重新将计数器重置为5,不如恢复计数。
问题在于,count.current
行中的if (count.current === 5) {
始终为0
,因为它不使用最新值。
我要使其正常工作的唯一方法是按以下方式调整代码:
const MyComponent = (props) => {
const count = React.useContext(MyContext);
const increaseByOne = React.useCallback(() => {
console.log(count.current);
if (count.current === 5) {
count.reset();
}
else {
count.increase(1);
}
}, [count]);
React.useEffect(() => {
console.log('useEffect');
const interval = setInterval(increaseByOne, 1000);
return () => {
clearInterval(interval);
};
}, [increaseByOne]);
return (
<div>{count.current}</div>
);
}
现在,increaseByOne
回调将在每次上下文更改时重新创建,这也意味着每秒都会调用该效果。
结果是,每次对上下文进行更改时,它都会清除间隔并设置一个新间隔(您可以在浏览器控制台中看到)。
在这个小示例中,这可能会起作用,但是它改变了原来的逻辑,并且开销更大。
我的应用程序不依赖于间隔,但它正在监听事件。删除事件侦听器并在以后再次添加它,这意味着,如果在删除和侦听器绑定之间触发了某些事件,则我可能会放开一些事件,这是由React异步完成的。
有人在不改变一般逻辑的情况下有一个想法,期望如何解决这个问题?
我在这里创建了一个小提琴,以处理上面的代码:
https://jsfiddle.net/Jens_Duttke/78y15o9p/
答案 0 :(得分:1)
如果increaseByOne
函数不需要知道实际的count.current
,则可以避免重新创建它。在上下文中,创建一个名为is
的新函数,该函数检查current
是否等于一个值:
is = n => this.state.current === n;
并在increaseByOne
函数中使用此函数:
if (count.is(5)) {
count.reset();
}
示例:
const MyContext = React.createContext();
class MyContextProvider extends React.Component {
render() {
return (
<MyContext.Provider value={this.state}>
{this.props.children}
</MyContext.Provider>
);
}
increase = (step) => {
this.setState((prevState) => ({
current: prevState.current + step
}));
}
reset = () => {
this.setState({
current: 0
});
}
is = n => this.state.current === n;
state = {
current: 0,
increase: this.increase,
reset: this.reset,
is: this.is
};
}
const MyComponent = (props) => {
const { increase, reset, is, current } = React.useContext(MyContext);
const increaseByOne = React.useCallback(() => {
if (is(5)) {
reset();
} else {
increase(1);
}
}, [increase, reset, is]);
React.useEffect(() => {
setInterval(increaseByOne, 1000);
}, [increaseByOne]);
return (
<div>{current}</div>
);
}
const App = () => (
<MyContextProvider>
<MyComponent />
</MyContextProvider>
);
ReactDOM.render( <
App / > ,
document.querySelector("#app")
);
body {
background: #fff;
padding: 20px;
font-family: Helvetica;
}
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div id="app"></div>
答案 1 :(得分:1)
第一个解决方案是将随时间变化的数据放入useRef
中,这样就可以通过引用而不是闭包来访问它(以及在基于类的版本中访问实际的this.state
)
const MyComponent = (props) => {
const countByRef = React.useRef(0);
countByRef.current = React.useContext(MyContext);
React.useEffect(() => {
setInterval(() => {
const count = countByRef.current;
console.log(count.current);
if (count.current === 5) {
count.reset();
} else {
count.increase(1);
}
}, 1000);
}, []);
return (
<div>{countByRef.current.current}</div>
);
}
另一种解决方案是修改reset
和increase
以允许函数自变量,并且可以使用setState
和useState
的更新程序来实现。
那应该是
useEffect(() => {
setInterval(() => {
count.increase(current => current === 5? 0: current + 1);
}, 1000);
}, [])
PS还希望您不要错过实际代码中的清理功能:
useEffect(() => {
const timerId = setInterval(..., 1000);
return () => {clearInterval(timerId);};
}, [])
否则,您将发生内存泄漏