Компоненты функции React повторно отображаются, когда что-либо меняется

#reactjs #redux #material-ui #redux-thunk

Вопрос:

Эти вещи с реакцией сводят меня с ума, я не помню, чтобы у меня давно были эти проблемы с компонентами класса, когда я работал над react native. Должно быть, я что-то здесь делаю не так, это одна из моих проблем, у меня есть еще один компонент, у которого также есть аналогичная проблема. Я новичок в новой системе крючков, даже читая о некоторых вещах, которые мне все еще не очень понятны.

У меня возникли некоторые проблемы с локальными состояниями и состояниями восстановления, из-за которых мой диалог мерцает. Каждый раз, когда родительский компонент устанавливал состояние журнала в этом цикле, диалоговое окно закрывалось и открывалось. (так что в этом случае 100 раз в зависимости от длины петли) Он перерисовывается каждый раз, когда обновляется состояние.

библиотеки реагируют/redux/redux-thunk/материал-пользовательский интерфейс

Пример Кода

 function ToolbarModal(props){
 const [progressLog, setProgressLog] = useState("");

 const openProgressDialog = useSelector(getOpenProgressDialog);
 const handleCloseLongProgressDialog = () => {
   dispatch(handleOpenBatchRouteProgressDialog(false))
 }

 const handleSomeLogic = () => {
  for(let i=0; i<0; i  ){
    //The real one is concated with prev message
   setProgressLog("SAMPLE MESSAGE")
  }
 }

 return (
  <div>
   <LongProgressDialog
     open={openProgressDialog}
     onClose={handleCloseLongProgressDialog}
     onEntered={handleSomeLogic}
     log={progressLog}
   />
  </div>
 )
}
 

Опора журнала передается дочернему компоненту с помощью диалогового компонента material-ui и просто отображается в текстовом поле.

 function LongProgressDialog(props){
    const {open, onClose, onEntered, log} = props

    return (
        <Dialog
            open={open}
            onClose={onClose}
            onEntered={onEntered}
            maxWidth='sm'
            fullWidth
        >
            <DialogTitle>Long Progress</DialogTitle>
            <DialogContent>
                <TextField 
                 multiline
                 fullWidth
                 rows={10}
                 rowsMax={10}
                 variant='outlined'
                 disabled
                 value={log}
                 />
            </DialogContent>
        </Dialog>
    )
}
 

Комментарии:

1. каждый раз , когда вы вызываете setProgressLog , вы изменяете переменную состояния и повторно визуализируете компонент. Именно так работает компонент react. Зачем вы используете переменную состояния, если не хотите, чтобы компонент повторно отображался?

2. есть ли способ, которым я только повторно визуализирую текстовое поле внутри LongProgressDialog, вместо того, чтобы также повторно визуализировать родительский объект? Потому что я пытаюсь перенаправить сообщение из этой логической функции на медленное создание журнала.

3. нет, если текстовое поле находится внутри LongProgressDialog, то LongProgressDialog также должен быть перезапущен. Хотя оттуда вы могли бы использовать такие, как React.memo на некоторых дочерних компонентах внутри LongProgressDialog, чтобы остановить их ненужную перенастройку (в зависимости от того, какие реквизиты они получают)

4. Когда вы меняете состояние, компоненты перерисовываются, но в вашем примере я думаю, что это не проблема. Если вы щелкаете диалоговым окном, это может быть проблемой в вашем компоненте LongProgressDialog. Я думаю, что вы тоже должны поделиться этим компонентом. Если переменная openProgressDialog изменяется при использовании функции setprograss, может возникнуть проблема с редуктором.

5. Вы должны добавить forwardRef в LongProgressDialog, вернуть что-то вроде функции changeText . Затем в ToolbarModal вызовите changeText в объекте useRef объекта LongProgressDialog

Ответ №1:

Я думаю, что вы хотите использовать useRef:

 import { useRef } from 'react'

const Component = props => {

  const logRef = useRef()
  const handleSomeLogic = () => {
  for(let i=0; i<100; i  ){
    logRef.current = "SAMPLE MESSAGE"
  }
 }
}
 

Затем передайте ссылку дочернему компоненту:

  <LongProgressDialog
     open={openProgressDialog}
     onClose={handleCloseLongProgressDialog}
     onEntered={handleSomeLogic}
     log={logRef.current}
   />
 

И в вашем детском компоненте:

 const LongProgressDialog = ({open, onClose, onEntered, log}) => {

  ...
  return (
      <p> Log value is: {log} </p>
     
   )
}
 

Однако,

Обратите внимание, что, поскольку компонент не будет повторно отрисован, вы не увидите обновлений в дочернем элементе, если компонент не будет отрисован. Таким образом, вы должны решить, исходя из вашего случая, следует ли вам обновить состояние или обновить ссылку и затем выполнить повторную визуализацию. Вы можете проверить пример codesandbox здесь.

Комментарии:

1. Вероятно, это могло бы сработать, но, как вы упомянули, его необходимо уведомлять об обновлении пользовательского интерфейса при его изменении. Я хотел бы продолжать смотреть, смогу ли я заставить это работать с государствами.

2. Тогда нет другого способа, кроме как изменить состояние. React не может обновить значение и преобразовать обновленное значение в jsx без повторного рендеринга.

Ответ №2:

Каждый раз, когда ссылка на объект, который вы передаете как свойство, изменяется, FC (функциональный компонент) будет повторно отображаться. Таким образом, чтобы предотвратить это, передаваемые вами реквизиты должны иметь одну и ту же ссылку при каждом повторном рендеринге.

Позвольте мне объяснить более подробно:

Например, здесь:

 const handleSomeLogic = () => {
//...
 

вы создаете новую функцию каждый раз, когда FC перерисовывается (что происходит при обновлении состояния).

Вы можете запомнить эту функцию с useCallback помощью .

Функции, объекты (и массивы) меняют свое значение каждый раз, когда вы их создаете, поэтому:

 const function1 = () => {}
const function2 = () => {}
function1 === function2 // false

const emptyObj = {}
const emptyObj2 = {}
emptyObj === emptyObj2 // false

// ... etc
 

Для того чтобы запомнить значения, вы должны использовать useMemo крючок.

Итак, в качестве решения: оберните все свои функции useCallback . Примечание: Я вижу, что вы передаете значение прогресса LongProgressDialog компоненту, поэтому технически каждый раз, когда вы обновляете прогресс LongProgressDialog , компонент будет повторно отображаться.

В дополнение к этому вы должны обернуть свой компонент React.memo . Когда родитель повторно визуализирует, ребенок тоже будет повторно визуализировать. React.memo предотвращает это и будет повторно отображать компонент только в том случае, если один из его реквизитов был изменен.

Выяснение того, какая опора вызывает проблему

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

Так, например:

 // create variables outside of the FC in which we will store the reference
let prop1Ref;
let prop2Ref;

const LongProgressDialog = ({ prop1, prop2 }) => {

  // for each prop, use a effect which will be called when the prop's value/reference changes
  useEffect(() => {
    // compare the values by reference
    if (prop1Ref !== prop1) {
      // when prop ref has a value, the reference must have been changed:
      if(prop1Ref != null) {
        console.log("The reference of prop1 has changed. Memorize it properly");
      }
      prop1Ref = prop1;
    }
  }, [prop1])


}
 

Вы можете скопировать логику и применить ее ко всем вашим реквизитам.
Если вы не хотите проверять это вручную, есть удобные библиотеки, такие как https://github.com/welldone-software/why-did-you-render

Комментарии:

1. Я завернул все свои функции в useCallback, но они, похоже, не делали ничего другого. Дело в том, что даже если я не передам значение прогресса в свой компонент LongProgressDialog, как только я обновлю состояние с помощью setProgressLog, оно будет повторно отображаться, что приведет к закрытию и открытию диалогового окна.

2. Пожалуйста, отредактируйте свой вопрос и добавьте код того, что getOpenProgressDialog делает. Я подозреваю, что возвращаемое значение этого не запоминается. Вы можете дополнительно попробовать обернуть LongProgressDialog React.memo . Я отредактирую свой ответ, как вы можете продолжить расследование того, какая опора вызывает повторный перевод.

3. getOpenProgressDialog возвращает только значение true/false из среза redux. Простой метод получения. const getOpeProgressDialog = состояние => состояние.ui.openProgressDialog

4. ладно, все в порядке. Затем проверьте мой обновленный ответ, похоже, вам нужно продолжить расследование того, что происходит

5. Я воспользовался вашим журналом. Открытый редуктор true/false изменяется только тогда, когда значение действительно изменилось. Так что этот редуктор работает правильно…

Ответ №3:

гу Минфэн ответил на него в своем комментарии. Просто используйте прямой реферат, исправил мою проблему, технически это то, что я искал все это время и не знал, что оно существует.

если кому-то это нужно, просто следуйте документу forward ref из документов react, и это очень просто.