繁体   English   中英

React Hook 的 componentWillReceiveProps、componentDidUpdate

[英]componentWillReceiveProps, componentDidUpdate for React Hook

我遇到了两个挑战:

  • 即使根据 React 指南,不鼓励派生状态,但某些边缘情况仍然需要它。
    就带有 React Hook 的功能组件而言,React Hook的等效实现是什么,如果我确实需要派生状态? 在类组件中,将在每个父渲染的 componentWillReceiveProps 中更新

见下面的代码示例:

 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。

  • 与旧的 LifeCycle 函数对齐,将内容重命名为 props 和 nextProps
  • nextProps 表示当前道具
  • props 表示之前的道具
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>
}

现在,如果您的按钮重新呈现,例如通过onClick新引用,您将得到如下内容: 在此处输入图片说明

我意识到您的“派生状态”示例是故意简单的,但是由于派生状态的合法情况很少,因此除了逐案之外,很难对替换提出建议,因为这取决于您的原因正在使用派生状态。 在您提供的特定示例中,没有理由在类情况下使用派生状态,因此在钩子情况下仍然没有理由(该值可以仅在本地派生而不将其置于状态)。 如果派生值很昂贵,您可以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>

编辑派生状态和 componentDidUpdate

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>

注 1componentWillReceiveProps已被弃用一段时间。 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM