ComponentWillReceiveProps, componentDidUpdate для 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 гарантирует, что код будет выполняться только при изменении пропса, но что, если я хочу выполнять соответствующие задачи на основе нескольких соответствующих изменений реквизита? как это сделать с использованиемЭффект?

см. пример кода ниже:

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.info('Let'
        's say, I do want to do some task here only when groupName differs');
    } else if (prevProps.companyName !== this.props.companyName) {
      console.info('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;
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
63
0
71 647
10

Ответы 10

Вы можете использовать хук 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.info("Let's say, I do want to do some task here only when groupName differs");
  }, [props.groupName])
  useEffect(() => {
    console.info("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>

Я понимаю, что ваш пример «производного состояния» намеренно прост, но поскольку существует так мало законных случаев производного состояния, трудно дать рекомендацию по замене, кроме как в каждом конкретном случае, поскольку это зависит от причины, по которой вы используют производное состояние. В приведенном вами конкретном примере не было причин использовать производное состояние в случае класса, и поэтому по-прежнему нет причин в случае ловушки (значение может быть просто получено локально, не помещая его в состояние). Если полученное значение дорого, вы можете использовать useMemo как подарок Tholle. Если они не соответствуют более реалистичным случаям, которые вы имеете в виду, вам нужно будет представить более конкретный случай, который действительно требует производного состояния.

Что касается вашего примера componentDidUpdate, если то, что вы хотите сделать для разных реквизитов, является независимым, то вы можете использовать отдельные эффекты для каждого (например, несколько вызовов useEffect). Если вы хотите сделать точно, как в вашем примере (т. е. сделать что-то только для companyName изменения, если groupName также не изменилось, как указано вашим 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.info("Do something when groupName changes", groupName);
      }
    },
    [groupName]
  );
  useEffect(
    () => {
      if (!initialRender.current) {
        console.info("Do something when companyName changes", companyName);
      }
    },
    [companyName]
  );
  useEffect(
    () => {
      if (!initialRender.current && groupName === lastGroupName.current)
        console.info(
          "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>

Edit Derived state and componentDidUpdate

В вашем сценарии вам вообще не нужно использовать или повторно реализовывать 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>;
};

не правда ли, что реквизит доступен только для чтения? и изменение реквизита также нарушает чистую функцию. правильно?

MPV 04.08.2019 09:06

Хук реакции, эквивалентный старому реквизиту componentWillReceive, можно сделать с помощью хука useEffect, просто указав реквизит, который мы хотим прослушивать на предмет изменений в массиве зависимостей.

То есть:

export default (props) => {

    useEffect( () => {
        console.info('counter updated');
    }, [props.counter])

    return <div>Hi {props.counter}</div>
}

Для componentDidUpdate просто опуская массив зависимостей, функция useEffect будет вызываться после каждого повторного рендеринга.

То есть:

export default (props) => {

    useEffect( () => {
        console.info('counter updated');
    })

    return <div>Hi {props.counter}</div>
}

Проведя обширное исследование по этому вопросу, я бы сказал, что ваше объяснение componentWillReceiveProps неверно. Итак, проблема в том, что замена componentWillReceiveProps сложна, потому что это функция предварительного рендеринга. Это не относится к реактивным хукам. Вы можете использовать useRef(), чтобы сохранить предыдущий реквизит и сравнить его с текущим в функции useEffect, чтобы попытаться воспроизвести функциональность, но даже это не может быть полной заменой 1:1.

Zach Pedigo 20.10.2020 17:09

setCount вызовет повторный рендеринг. Использование useEffect с [count] в качестве массива зависимостей гарантирует, что хук будет вызывать setCount только при изменении значения count.

Вот как вы заменяете любую componentWillReceiveProps логику, которую вы могли бы написать в старом стиле React, основанном на классах. Я считаю полезным принцип «Каждый рендер имеет свои реквизиты и состояние»: если вы хотите инициировать повторный рендеринг только при изменении определенных свойств, у вас может быть несколько хуков useEffect.

useEffect(() => {
  count > 100 ? setCount(100) : setCount(count)
}, [count]) 
 
useEffect(() => {
  console.info('groupName has changed');
  // do something with groupName
}, [groupName])
    
useEffect(() => {
  console.info('companyName has changed');
  // do something with companyName
}, [companyName]) 

просто используя 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.

1.) What is the equivalent implementation with React Hook, If I do need derived state?

Производное состояние для хуков = установить состояние условно и напрямую на этапе рендеринга:

constComp = (props) => {
  const [derivedState, setDerivedState] = useState(42);
  if (someCondition) {
    setDerivedState(...);
  }
  // ...
}

Это обновляет состояние без дополнительной фазы фиксации, в отличие от useEffect. Приведенный выше шаблон поддерживается Реагировать на строгий режим (без предупреждений):

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.info("render, prevRow:", prevRow)

  if (row !== prevRow) {
    console.info("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.) What if I want to do respective tasks based on multiple respective props changes?

Вы можете либо полностью убрать useEffect deps, либо добавить еще один реквизит:

React.useEffect(() => {
  return () => { };
}, [parentProp, secondProp]);

Если вы используете хук useMemo поверх вашего компонента и он зависит от всех ваших реквизитов, он запускается перед всем при каждом изменении реквизитов. useEffect срабатывает после обновленного рендеринга и, поскольку зависит от всех реквизитов, он срабатывает после повторного рендеринга в зависимости от всех реквизитов.

const Component = (...props) => {
   // useState, useReducer if have
   useMemo(() => {
     // componentWillReceiveProps
   },[...props]);
   // ...other logic and stuff
   useEffect(() => {
     // componentDidUpdate
   }, [...props]);
};

Я рад узнать, что это действительно полезно, чтобы определить разницу между ними. Спасибо ..!

Sanat Gupta 15.10.2020 19:45

В случае, если вы хотите реплицировать систему журналов, такую ​​как почему ты рендерил, и вам нужен nextProps здесь помогает хук.

  • в соответствии со старой функцией lifeCycle переименуйте вещи в props и nextProps
  • 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.info(
        `%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 новой ссылке вы получите что-то вроде этого:

Я использую 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()

Это вызывается в верхней части функционального компонента. Таким образом, его следует вызывать до того, как функциональный компонент отобразит свое представление.

Другие вопросы по теме