Сложность понимания значения удаления функций со стрелками () =>

#javascript #react-native

Вопрос:

Когда я ищу в Интернете react-native оптимизацию / лучшие практики (особенно для FlatLists которых часто бывают жадными), я всегда нахожу совет не использовать функции со стрелками <Component onPress={() => ... } .

https://reactnative.dev/docs/optimizing-flatlist-configuration#avoid-anonymous-function-on-renderitem Пример 1 : :

Переместите функцию renderItem за пределы функции рендеринга, чтобы она не воссоздавалась при каждом вызове функции рендеринга. (…)

https://blog.codemagic.io/improve-react-native-app-performance/ Example 2 : :

Избегайте функций со стрелками : Функции со стрелками являются распространенной причиной расточительного повторного отображения. Не используйте функции со стрелками в качестве обратных вызовов в своих функциях для визуализации представлений (…)

https://medium.com/wix-engineering/dealing-with-performance-issues-in-react-native-b181d0012cfa Пример 3 : :

Функции со стрелками-еще один обычный подозреваемый в расточительном повторном рендеринге. Не используйте функции со стрелками в качестве обратных вызовов (например, нажмите/коснитесь) в своих функциях визуализации (…)

Я понимаю, что рекомендуется не использовать функцию стрелки (особенно в onPress кнопке и FlatList ), а по возможности выводить компоненты за пределы рендеринга.

Пример хорошей практики :

 const IndexScreen = () => {    
  const onPress = () => console.log('PRESS, change state, etc...')

  return (
    <>
      <Button
        onPress={onPress}
      />
      <FlatList
        ...
        renderItem={renderItem}
        ListFooterComponent={renderFooter}
      />
    </>
  )
}

const renderItem = ({ item: data }) => <Item data={data} ... />
const renderFooter = () => <Footer ... />

export default IndexScreen
 

Но часто у меня есть другие свойства для интеграции в мои дочерние компоненты. Поэтому функция стрелки является обязательной:

 const IndexScreen = () => {
  const otherData = ...(usually it comes from a useContext())...

  <FlatList
    ...
    renderItem={({ item: data }) => renderItem(data, otherData)}
  />
}

const renderItem = (data, otherData) => <Item data={data} otherData={otherData} />

export default IndexScreen
 

В последней ситуации соблюдается ли надлежащая практика, несмотря на наличие функции стрелки ?
Таким образом, если я удалю otherData (для простоты), являются ли эти две ситуации строго идентичными и соблюдаются ли рекомендации ?

Ситуация 1 :

 const IndexScreen = () => {    
  return (
    <FlatList
      ...
      renderItem={renderItem}
    />
  )
}

const renderItem = ({ item: data }) => <Item data={data} ... />

export default IndexScreen
 

=== Ситуация 2 ?

 const IndexScreen = () => {    
  return (
    <FlatList
      ...
      renderItem={({ item: data }) => renderItem(data)}
    />
  )
}

const renderItem = (data) => <Item data={data} ... />

export default IndexScreen
 

Ответ №1:

Ответ не имеет ничего общего с функциями стрелок, а скорее с пониманием равенства ссылок, почему react может принять решение о повторной передаче компонента.

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

 const renderItem = useCallback(()=>{
...
},
[otherdata]);
 

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

1. Большое вам спасибо за ваш ответ 🙂 Вы уверены, что это единственная причина? Я знаком с useCallback / useMemo и PureComponent знаю , что их следует использовать с осторожностью. Однако функции со стрелками всегда не рекомендуются (о чем свидетельствуют мои три цитаты), независимо от useCallback

2. Это не имеет никакого отношения к синтаксису стрелок. React будет повторно отображать компонент всякий раз, когда ссылка изменяется на реквизит или состояние. Если вы воссоздаете функцию при каждом рендеринге, а затем передаете эту новую ссылку в качестве опоры в дочерний компонент, этот дочерний компонент также будет повторно отрисован. useCallback вернет новую ссылку на свой первый параметр только при обновлении зависимостей. Создание функции вне цикла рендеринга (например, в модуле) приведет к созданию только одной ссылки на эту функцию (или, я думаю, независимо от того, загружен ли модуль).

Ответ №2:

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

 renderItem={({ item: data }) => renderItem(data)}
 

Следовательно, каждый раз повторяйте отправку списка.

Чтобы исправить это, вам нужно запомнить функцию, которую вы передаете в renderItem реквизите с помощью useCallback

 const renderItem  = useCallback(({ item: data }) => {
   return (<Item data={data} />)
}, []);

...

    <FlatList
      ...
      renderItem={renderItem}
    />
 

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

 const renderItem  = useCallback(({ item: data }) => {
   return (<Item data={data} otherData={otherData} />)
}, [otherData]);
 

Ответ №3:

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

 const renderItem  = useCallback( function renderItemFunction ({ item: data }) {
   return (<Item data={data} otherData={otherData} />)
}, [otherData]);
 

таким образом, в трассировке стека ошибок вы должны увидеть renderItemFunction указание