![](/img/trans.png)
[英]componentDidUpdate vs componentWillReceiveProps use case in react
[英]componentWillReceiveProps, componentDidUpdate for React Hook
我遇到了两个挑战:
见下面的代码示例:
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,componentDidUpdate在使用React Hook时有它的对应物,你必须像这样使用它,
React.useEffect(() => { return () => { }; }, [parentProp]);
useEffect 的第二个参数确保代码仅在 prop 更改时执行,但是如果我想根据多个相应的 props 更改执行相应的任务怎么办? 如何使用 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;
相当于旧的componentWillReceive
道具的useEffect
钩子可以使用useEffect
钩子来完成,只需指定我们要监听依赖数组中的变化的道具。
IE:
export default (props) => {
useEffect( () => {
console.log('counter updated');
}, [props.counter])
return <div>Hi {props.counter}</div>
}
对于componentDidUpdate
仅仅通过省略依赖数组, useEffect
函数将在每次重新渲染后调用。
IE:
export default (props) => {
useEffect( () => {
console.log('counter updated');
})
return <div>Hi {props.counter}</div>
}
您可以使用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>
在单独的 props 更改时执行单独的效果的最简单方法是创建多个useEffect
钩子,这些钩子仅在单独的 props 之一更改时运行。
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>
setCount
将触发重新渲染。 使用useEffect
和[count]
作为依赖项数组将确保钩子只会在count
的值更改时调用setCount
。
这就是你如何替换任何你可能以旧的基于类的 React 风格编写的componentWillReceiveProps
逻辑。 我发现“ 每个渲染都有自己的道具和状态”原则很有用:如果您只想在特定道具更改时触发重新渲染,您可以有几个useEffect
钩子。
useEffect(() => {
count > 100 ? setCount(100) : setCount(count)
}, [count])
useEffect(() => {
console.log('groupName has changed');
// do something with groupName
}, [groupName])
useEffect(() => {
console.log('companyName has changed');
// do something with companyName
}, [companyName])
如果您在组件顶部使用 useMemo 钩子并使其依赖于您的所有道具,则每次道具更改时它都会在所有内容之前运行。 useEffect 在更新渲染后触发,并且由于依赖于所有道具,因此它在根据所有道具重新渲染后触发。
const Component = (...props) => {
// useState, useReducer if have
useMemo(() => {
// componentWillReceiveProps
},[...props]);
// ...other logic and stuff
useEffect(() => {
// componentDidUpdate
}, [...props]);
};
在您的场景中,您根本不需要使用或重新实现getDerivedStateFromProps
。 您只需要创建一个新变量即可获取新形式的数据。 在这种情况下使用 state 只会导致另一次重新渲染,这在性能上不是很好。
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 : getDerivedStateFromProps
如果你真的需要使用一个单独的状态,你可以使用这样的东西
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>;
};
只需像这样使用 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.
在某种情况下,您想要复制一个日志系统,例如 您为什么渲染,并且您需要在这里使用nextProps
来帮助 hook。
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>
}
我意识到您的“派生状态”示例是故意简单的,但是由于派生状态的合法情况很少,因此除了逐案之外,很难对替换提出建议,因为这取决于您的原因正在使用派生状态。 在您提供的特定示例中,没有理由在类情况下使用派生状态,因此在钩子情况下仍然没有理由(该值可以仅在本地派生而不将其置于状态)。 如果派生值很昂贵,您可以useMemo
一样使用useMemo
。 如果这些不适合您想到的更现实的情况,您将需要提出一个真正需要派生状态的更具体的情况。
就您的componentDidUpdate
示例而言,如果您要为不同的道具做的事情是独立的,那么您可以为每个道具使用单独的效果(即多个useEffect
调用)。 如果您想完全按照您的示例进行操作(即,如果groupName
也没有按照您的else if
指示的那样更改,则只为companyName
更改做一些事情),那么您可以将refs用于更复杂的条件。 你不应该在渲染过程中发生变异裁判(总有渲染被丢弃/恢复的可能性一旦支持的并发模式),这样的例子使用最后的效果进行更新,裁判。 在我的示例中,我使用 ref 来避免对初始渲染执行效果工作(请参阅 Tholle 在此相关问题中的回答),并在决定是否根据companyName
更改执行工作时检测groupName
是否更改。
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>
1.) React Hook 的等效实现是什么,如果我确实需要派生状态?
Hooks 的派生状态= 在渲染阶段有条件地直接设置状态:
constComp = (props) => {
const [derivedState, setDerivedState] = useState(42);
if (someCondition) {
setDerivedState(...);
}
// ...
}
与useEffect
相比,这无需额外的提交阶段即可更新状态。 以上模式由React Strict 模式支持(无警告):
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 :在使用派生状态之前检查首选解决方案。
2.) 如果我想根据多个各自的道具变化来做各自的任务怎么办?
您可以完全不使用useEffect
deps 或最好添加另一个 prop dep:
React.useEffect(() => {
return () => { };
}, [parentProp, secondProp]);
我正在使用 React Native。 我将此添加到我需要 componentWillReceiveProps 功能的屏幕组件中:
const propsRef = useRef(props)
// component will receive props
const componentWillReceiveProps = () => {
const propsHaveChanged = !isEqual(propsRef.current, props)
propsRef.current = props
if (propsHaveChanged){
setIndex(0)
}
}
这里isEqual
是一个进行深度比较的 lodash 函数。 然后只需调用该函数。
componentWillReceiveProps()
这在功能组件的顶部调用。 所以,它应该在功能组件呈现它的视图之前调用
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.