Вам, вероятно, не нужен useEffect

фон

Я считаю, что у каждого есть такой опыт при написании React: в проекте используется много useEffect, так что наш код становится запутанным и сложным в обслуживании.

Может быть, хук useEffect не годится? Это не так, просто мы злоупотребляли этим.

В этой статье я покажу, как использовать другие методы вместо useEffect.

В чем пользаЭффект

useEffect позволяет выполнять побочные эффекты в функциональных компонентах. Он может имитировать componentDidMount, componentDidUpdate и componentWillUnmount. Мы можем сделать с ним много вещей. Но это также очень опасный хук, который может вызвать множество ошибок.

Почему useEffect подвержен ошибкам

Рассмотрим пример таймера:

import React, { useEffect } from 'react'

const Counter = () => {
  const [count, setCount] = useState(0)

  useEffect(() => {
    const timer = setInterval(() => {
      setCount((c) => c + 1)
    }, 1000)
  })

  return <div>{count}</div>
}
复制代码

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

Как это исправить?

useRef

import React, { useEffect, useRef } from 'react'

const Counter = () => {
  const [count, setCount] = useState(0)
  const timerRef = useRef()

  useEffect(() => {
    timerRef.current = setInterval(() => {
      setCount((c) => c + 1)
    }, 1000)
    return () => clearInterval(timerRef.current)
  }, [])

  return <div>{count}</div>
}
复制代码

Он не устанавливает таймер каждый раз при повторном рендеринге компонента. Но такого простого кода у нас в проекте нет. Скорее, он находится в разных состояниях и делает разные вещи.

Вы думали, что написали useEffect

useEffect(() => {
  doSomething()

  return () => cleanup()
}, [whenThisChanges])
复制代码

На самом деле это так

useEffect(() => {
  if (foo && bar && (baz || quo)) {
    doSomething()
  } else {
    doSomethingElse()
  }
  // 遗忘清理函数。
}, [foo, bar, baz, quo, ...])
复制代码

Напишите кучу логики, такой код очень грязный и сложный в обслуживании.

Для чего используется useEffect?

useEffect — это способ синхронизации React с какой-то внешней системой (сетью, подпиской, DOM). Если у вас нет внешних систем и вы просто пытаетесь управлять потоком данных с помощью useEffect, вы столкнетесь с проблемами.

Иногда нам не нужен useEffect

1. Нам не нужен useEffect для преобразования данных

const Cart = () => {
  const [items, setItems] = useState([])
  const [total, setTotal] = useState(0)

  useEffect(() => {
    setTotal(items.reduce((total, item) => total + item.price, 0))
  }, [items])

  // ...
}
复制代码

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

const Cart = () => {
  const [items, setItems] = useState([])
  const [total, setTotal] = useState(0)

  const totalNum = items.reduce((total, item) => total + item.price, 0)

  // ...
}
复制代码

Если логика расчета более сложная, можно использовать useMemo:

const Cart = () => {
  const [items, setItems] = useState([])
  const total = useMemo(() => {
    return items.reduce((total, item) => total + item.price, 0)
  }, [items])

  // ...
}
复制代码

2. Используйте useSyncExternalStore вместо useEffect

использованиеSyncExternalStore

Общий способ:

const Store = () => {
  const [isConnected, setIsConnected] = useState(true)

  useEffect(() => {
    const sub = storeApi.subscribe(({ status }) => {
      setIsConnected(status === 'connected')
    })

    return () => {
      sub.unsubscribe()
    }
  }, [])

  // ...
}
复制代码

Лучший способ:

const Store = () => {
  const isConnected = useSyncExternalStore(
    storeApi.subscribe,
    () => storeApi.getStatus() === 'connected',
    true
  )

  // ...
}
复制代码

3. Нет необходимости использовать useEffect для связи с родительским компонентом

const ChildProduct = ({ onOpen, onClose }) => {
  const [isOpen, setIsOpen] = useState(false)

  useEffect(() => {
    if (isOpen) {
      onOpen()
    } else {
      onClose()
    }
  }, [isOpen])

  return (
    <div>
      <button
        onClick={() => {
          setIsOpen(!isOpen)
        }}
      >
        Toggle quick view
      </button>
    </div>
  )
}
复制代码

Вместо этого вы можете использовать обработчики событий:

const ChildProduct = ({ onOpen, onClose }) => {
  const [isOpen, setIsOpen] = useState(false)

const handleToggle = () => {
  const nextIsOpen = !isOpen;
  setIsOpen(nextIsOpen)

  if (nextIsOpen) {
    onOpen()
  } else {
    onClose()
  }
}

  return (
    <div>
      <button
        onClick={}
      >
        Toggle quick view
      </button>
    </div>
  )
}
复制代码

4.没必要使用useEffect初始化应用程序

const Store = () => {
  useEffect(() => {
    storeApi.authenticate()
  }, [])

  // ...
}
复制代码

更好的方式:

方式一:

const Store = () => {
  const didAuthenticateRef = useRef()

  useEffect(() => {
    if (didAuthenticateRef.current) return

    storeApi.authenticate()

    didAuthenticateRef.current = true
  }, [])

  // ...
}
复制代码

方式二:

let didAuthenticate = false

const Store = () => {
  useEffect(() => {
    if (didAuthenticate) return

    storeApi.authenticate()

    didAuthenticate = true
  }, [])

  // ...
}
复制代码

方式三:

if (typeof window !== 'undefined') {
  storeApi.authenticate()
}

const Store = () => {
  // ...
}
复制代码

5.没必要在useEffect请求数据

常见写法

const Store = () => {
  const [items, setItems] = useState([])

  useEffect(() => {
    let isCanceled = false

    getItems().then((data) => {
      if (isCanceled) return

      setItems(data)
    })

    return () => {
      isCanceled = true
    }
  })

  // ...
}
复制代码

更好的方式:

没有必要使用useEffect,可以使用swr:

import useSWR from 'swr'

export default function Page() {
  const { data, error } = useSWR('/api/data', fetcher)

  if (error) return <div>failed to load</div>
  if (!data) return <div>loading...</div>

  return <div>hello {data}!</div>
}
复制代码

使用react-query:

import { getItems } from './storeApi'
import { useQuery, useQueryClient } from 'react-query'

const Store = () => {
  const queryClient = useQueryClient()

  return (
    <button
      onClick={() => {
        queryClient.prefetchQuery('items', getItems)
      }}
    >
      See items
    </button>
  )
}

const Items = () => {
  const { data, isLoading, isError } = useQuery('items', getItems)

  // ...
}
复制代码

没有正式发布的react的 use函数

function Note({ id }) {
  const note = use(fetchNote(id))
  
  return (
    <div>
      <h1>{note.title}</h1>
      <section>{note.body}</section>
    </div>
  )
}
复制代码

reference

www.youtube.com/watch?v=bGz…

Guess you like

Origin juejin.im/post/7182110095554117692