我遇到了两个挑战:
请参见下面的代码示例:
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;
至于componentDidUpdate,当使用React Hook时,componentDidUpdate就是它的合作伙伴,它是
React.useEffect(()=> { return()=> {
};
}, [parentProp]);
useEffect的第二个参数可确保仅在更改道具时才执行代码,但是如果我要根据多个道具的不同来执行各自的任务,该怎么办? 如何通过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;
答案 0 :(得分:3)
可以使用useEffect钩子完成相当于旧componentWillReceive道具的react钩子,只需指定我们要侦听依赖项数组中的更改的道具即可。
类似的东西:
export default (props) => {
useEffect( () => {
console.log('counter updated');
}, [props.counter])
return <div>Hi {props.counter}</div>
}
对于componentDidUpdate,只需省略依赖项数组,则每次重新渲染后都会调用useEffect函数。
答案 1 :(得分:2)
您可以使用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>
答案 2 :(得分:1)
在您的情况下,您根本不需要使用或重新实现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>;
};
答案 3 :(得分:1)
setCount将使用useEffect触发重新渲染,并且依赖项数组[count]将指定当count的值不同时,仅对count更新进行“监视”。 因此,可以以某种方式替换componentWillReceiveProps。 我们可以说“ Each Render Has Its Own Props and State”。如果要根据道具更改触发重新渲染,则可以具有多个useEffect。
/*componentWillReceiveProps*/
useEffect(() => {
count > 100 ? setCount(100) : setCount(count)
}, [count])
useEffect(() => {
console.log(`Let's say, I do want to do some task here only when groupName differs`);
}, [groupName])
useEffect(() => {
console.log(`Let''s say,I do want to do some different task here only when companyName differs`);
}, [companyName])
答案 4 :(得分:1)
只需使用这样的useEffect。
useEffect( () => {
props.actions.fetchSinglePost(props.match.params.id); //> I'm dispatching an action here.
}, [props.comments]) //> and here to watch comments and call the action in case there is any change.
答案 5 :(得分:1)
如果在组件顶部使用useMemo挂钩,并使它依赖于所有道具,则它将在每次道具更改之前运行。 useEffect在更新的渲染之后触发,并且由于依赖于所有道具,因此会在重新渲染之后根据所有道具触发。
const Component = (...props) => {
// useState, useReducer if have
useMemo(() => {
// componentWillReceiveProps
},[...props]);
// ...other logic and stuff
useEffect(() => {
// componentDidUpdate
}, [...props]);
};
答案 6 :(得分:1)
在某种情况下,您想要复制一个日志系统,例如 why did you render,并且您需要 nextProps
在这里帮助钩子。
const useComponentWillReceiveProps(nextProps, callback) {
const props = useRef(nextProps)
useEffect(() => {
callback(nextProps, props.current)
props.current = nextProps
})
}
const diffLogger = (nextProps, props, { name = "Component" } = {}) => {
Object.keys(nextProps)
.filter((key) => nextProps[key] !== props[key])
.forEach((key) => {
console.log(
`%c${name}:%c${key}`,
"color:#ff5722; font-size:1rem; font-weight:bold;",
"color:#ffc107; font-size:1.2rem; font-weight:bold;",
{
from: props[key],
to: nextProps[key],
}
)
})
}
const Button = (props) => {
useComponentWillReceiveProps(props, diffLogger, {name: 'Button'})
return <button onClick={props.onClick}>Button</button>
}
答案 7 :(得分:0)
我意识到您的“派生状态”示例在故意上很简单,但是由于派生状态的合法案例很少,因此很难提出替代建议,除非是逐案处理,因为它取决于您使用派生状态的原因。在您提供的特定示例中,没有理由在类情况下使用派生状态,因此在挂钩情况下也没有理由(值可以在本地派生而无需将其置于状态)。如果派生的值昂贵,则可以使用Tholle提出的useMemo
。如果这些都不适合您考虑的更实际的情况,则需要提供一个更具体的情况,该情况确实需要派生状态。
就您的componentDidUpdate
示例而言,如果您要为不同道具做的事情是独立的,则可以为每个道具使用单独的效果(即多个useEffect
调用)。如果您想精确地进行示例操作(即,如果companyName
也没有按照您的{{1}指示进行更改,则仅对groupName
进行更改}),那么您可以将refs用于更复杂的条件。渲染期间,您不应该不对参考进行变异(一旦支持并发模式,总是有可能丢弃渲染/重做渲染),因此该示例使用最后一种效果对参考进行更新。在我的示例中,我使用ref来避免在初始渲染上进行效果工作(请参阅this related question中的Tholle的回答),并在根据以下决定决定是否进行工作时检测else if
是否发生了变化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);
答案 8 :(得分:0)
1。)如果我确实需要派生状态,React Hook的等效实现是什么?
Derived state for Hooks =有条件地设置状态,并在渲染阶段直接设置 :
constComp = (props) => {
const [derivedState, setDerivedState] = useState(42);
if (someCondition) {
setDerivedState(...);
}
// ...
}
与useEffect
相比,此更新状态无需附加提交阶段。 React Strict Mode支持上述模式(无警告):
const App = () => {
const [row, setRow] = React.useState(1);
React.useEffect(() => {
setTimeout(() => {
setRow(2);
}, 3000);
}, []);
return (
<React.StrictMode>
<Comp row={row} />
</React.StrictMode>
);
}
const Comp = ({ row }) => {
const [isScrollingDown, setIsScrollingDown] = React.useState(false);
const [prevRow, setPrevRow] = React.useState(null);
console.log("render, prevRow:", prevRow)
if (row !== prevRow) {
console.log("derive state");
// Row changed since last render. Update isScrollingDown.
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
};
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js" integrity="sha256-4gJGEx/zXAxofkLPGXiU2IJHqSOmYV33Ru0zw0TeJ30=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.development.min.js" integrity="sha256-9xBa2Hcuh2S3iew36CzJawq7T9iviOAcAVz3bw8h3Lo=" crossorigin="anonymous"></script>
<div id="root"></div>
注释1 :componentWillReceiveProps
已被弃用一段时间。 getDerivedStateFromProps
是类组件的派生状态的继承者。
注释2 :Check preferred solutions,然后再求助于派生状态。
2。)如果我想根据多个道具变更来做各自的任务怎么办?
您可以完全不使用useEffect
部门,或者最好添加另一个道具部门:
React.useEffect(() => {
return () => { };
}, [parentProp, secondProp]);