我遇到了两个挑战:
见下面的代码示例:
class App extends Component {
constructor(props) {
super(props);
this.state = {
count: props.count > 100 ? 100 : props.count,
}
}
/*What is the equivalent implementation when React Hook is used here componentWillReceiveProps*/
componentWillReceiveProps(nextProps) {
if (nextProps.count !== this.props.count) {
this.setState({
count: nextProps.count > 100 ? 100 : nextProps.count
});
}
}
render() {
return ( <
div > {
this.state.count
} <
/div>
);
}
}
export default App;
};
}, [parentProp]);
useEffect的第二个参数确保代码仅在prop改变时执行,但是如果我想根据多个各自的道具差异执行各自的任务呢?如何用useEffect完成它?见下面的代码示例:
class App extends Component {
/*What is the equivalent implementation when functional component with React Hook is used here */
componentDidUpdate(prevProps, prevState) {
if (prevProps.groupName !== this.props.groupName) {
console.log('Let'
's say, I do want to do some task here only when groupName differs');
} else if (prevProps.companyName !== this.props.companyName) {
console.log('Let'
's say,I do want to do some different task here only when companyName differs');
}
}
render() {
/*for simplicity, render code is ignored*/
return null;
}
}
export default App;
您可以使用useMemo
钩子来存储计算,并将props.count
放在作为第二个参数给出的数组中,以便在更改时重新计算该值。
const { useState, useEffect, useMemo } = React;
function App() {
const [count, setCount] = useState(50);
useEffect(() => {
setTimeout(() => {
setCount(150);
}, 2000);
}, []);
return <DisplayCount count={count} />;
}
function DisplayCount(props) {
const count = useMemo(() => props.count > 100 ? 100 : props.count, [props.count]);
return <div> {count} </div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
单独的道具更改时,最简单的方法是创建单独的效果,即创建多个useEffect
钩子,只有在其中一个单独的道具发生变化时才会运行。
const { useState, useEffect } = React;
function App() {
const [groupName, setGroupName] = useState('foo');
const [companyName, setCompanyName] = useState('foo');
useEffect(() => {
setTimeout(() => {
setGroupName('bar');
}, 1000);
setTimeout(() => {
setCompanyName('bar');
}, 2000);
}, []);
return <DisplayGroupCompany groupName={groupName} companyName={companyName} />;
}
function DisplayGroupCompany(props) {
useEffect(() => {
console.log("Let's say, I do want to do some task here only when groupName differs");
}, [props.groupName])
useEffect(() => {
console.log("Let's say,I do want to do some different task here only when companyName differs");
}, [props.companyName])
return <div> {props.groupName} - {props.companyName} </div>;
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://unpkg.com/react@16/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
在您的场景中,您根本不需要使用或重新实现getDerivedStateFromProps
。您只需创建一个新变量即可获得新的数据形式。在这种情况下使用状态只会导致另一次重新渲染,这在性能上并不好。
import React from 'react';
const App = ({ count }) => {
const derivedCount = count > 100 ? 100 : count;
return (
<div>Counter: {derivedCount}</div>
);
}
App.propTypes = {
count: PropTypes.number.isRequired
}
在这里演示:https://codesandbox.io/embed/qzn8y9y24j?fontsize=14
您可以在不使用getDerivedStateFromProps
的情况下阅读更多关于解决这些场景的不同方法:https://reactjs.org/blog/2018/06/07/you-probably-dont-need-derived-state.html
如果你真的需要使用一个单独的状态,你可以使用这样的东西
import React, { useState } from 'react';
const App = ({ count }) => {
const [derivedCounter, setDerivedCounter] = useState(
count > 100 ? 100 : count
);
useEffect(() => {
setDerivedCounter(count > 100 ? 100 : count);
}, [count]); // this line will tell react only trigger if count was changed
return <div>Counter: {derivedCounter}</div>;
};
我意识到你的“派生状态”示例是故意简单的,但由于衍生状态的合法案例很少,因此除了逐个案例之外很难对替换提出建议,因为它取决于你的原因正在使用派生状态。在您提供的特定示例中,没有理由在类情况下使用派生状态,因此在钩子情况下仍然没有理由(该值可以在本地派生而不将其置于状态)。如果派生值很昂贵,您可以使用useMemo
作为Tholle呈现。如果这些不适合您想到的更现实的案例,则需要提出一个真正需要派生状态的更具体的案例。
至于你的componentDidUpdate
例子,如果你想为不同的道具做什么是独立的,那么你可以为每个道具使用单独的效果(即多个useEffect
调用)。如果你想要完全按照你的例子做什么(即如果你的companyName
表明,如果groupName
也没有改变的话,只做else if
改变的事情),那么你可以使用refs来获得更复杂的条件。你不应该在渲染过程中改变ref(在支持并发模式时总是有可能丢弃/重做渲染),因此该示例使用最后一个效果来对ref进行更新。在我的例子中,我使用ref来避免对初始渲染进行效果工作(参见Tholle在this related question中的答案),并在决定是否根据groupName
更改进行工作时检测companyName
是否发生了变化。
const { useState, useEffect, useRef } = React;
const DerivedStateFromProps = ({ count }) => {
const derivedCount = count > 100 ? 100 : count;
return (
<div>
Derived from {count}: {derivedCount}{" "}
</div>
);
};
const ComponentDidUpdate = ({ groupName, companyName }) => {
const initialRender = useRef(true);
const lastGroupName = useRef(groupName);
useEffect(
() => {
if (!initialRender.current) {
console.log("Do something when groupName changes", groupName);
}
},
[groupName]
);
useEffect(
() => {
if (!initialRender.current) {
console.log("Do something when companyName changes", companyName);
}
},
[companyName]
);
useEffect(
() => {
if (!initialRender.current && groupName === lastGroupName.current)
console.log(
"Do something when companyName changes only if groupName didn't also change",
companyName
);
},
[companyName]
);
useEffect(
() => {
// This effect is last so that these refs can be read accurately in all the other effects.
initialRender.current = false;
lastGroupName.current = groupName;
},
[groupName]
);
return null;
};
function App() {
const [count, setCount] = useState(98);
const [groupName, setGroupName] = useState("initial groupName");
const [companyName, setCompanyName] = useState("initial companyName");
return (
<div>
<div>
<DerivedStateFromProps count={count} />
<button onClick={() => setCount(prevCount => prevCount + 1)}>
Increment Count
</button>
</div>
<div>
<ComponentDidUpdate groupName={groupName} companyName={companyName} />
groupName:{" "}
<input
type="text"
value={groupName}
onChange={event => setGroupName(event.target.value)}
/>
<br />
companyName:{" "}
<input
type="text"
value={companyName}
onChange={event => setCompanyName(event.target.value)}
/>
<br />
change both{" "}
<input
type="text"
onChange={event => {
const suffix = event.target.value;
setGroupName(prev => prev + suffix);
setCompanyName(prev => prev + suffix);
}}
/>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<div id="root"></div>
<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>