Компоненты собственно бывают двух типов
Для начала возьмем компонент написанный на классе:
class SmartComponent extends Componennt {
static defaultProps = {
page: 1,
}
constructor(props) {
super(props)
this.state = {
data: [],
loading: true,
}
}
componentDidMount() {
this.fetchData()
}
componentDidUpdate(prevProps) {
if (this.props.page !== prevProps.page) {
this.fetchData()
}
}
fetchData() {
api.fetchData(this.props.page)
.then(data => {
this.setState({ data, loading: false })
})
}
render() {
if (this.state.loading) {
return <div>Loading...</div>
}
return (
<div>
{this.state.data.map(value => (
<div>{value}</div>
))}
</div>
)
}
}
и перепишем его на функциональный компонент, что нам для этого нужно?
- Указать, что есть дефолтное свойство для page.
- Создать два стейта для
data
иloading
. - Вызвать фетч данных при маунте и при обновлении.
И так, чтобы создать локальный стейт необходимо воспользоваться функцией useState
, она в качестве аргумента принимает собственно первоначальное состояние (так же хочется сказать, что не стоит запихивать в useState
весь шейп стейта, лучше сделать несколько отдельных стейтов) и возввращает тапл, где первый элемент является стейт, а второй элемент является диспатчером, который этот стейт обновлят:
Плохой пример:
// Так делать плохо, т.к. нужно будет копировать шейп и изменять только нужное состояние:
const [state, setState] = useState({ data: [], loading: true })
setState({ ...state, loading: false })
Хороший пример:
// Так делать хорошо, т.к. мы обновляем нужный нам кусок стейта
const [data, setData] = useState([])
const [loading, setLoading] = useState(false)
setData([])
setLoading(false)
Далее, чтобы реагировать на какое-то изменение в нашем вью мы будем использовать useEffect
, он принимает два аргумента, первый это колбек которйы будет вызван и второй это набор зависимых свойств при изменении которых будет вызван колбек.
Пример как реагировать при изменении конкретно свойства:
// Колбек будет вызван первый раз при маунте и в последующий раз когда изменится page
useEffect(() => {
...
}, [page])
Пример как реагировать при изменении любого свойства:
// В качестве второго аргумента не передаем массив, это означает то, что нужно вызывать колбек всегда
useEffect(() => {
...
})
Пример как реагировать только при маунте:
// В качестве второго аргумента передаем пустой массив, в итоге выполнится один раз при маунте
useEffect(() => {
...
}, [])
И так, приступаем к написанию компонента:
const SmartComponent = ({ page = 1 }) => {
const [data, setData] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
api.fetchData(page)
.then(data => {
setData(data)
setLoading(false)
})
// Тут нам уже не надо проверять прошлый page и текущий, т.к. эта логика инкапсулирована внутри.
}, [page])
if (loading) {
return <div>Loading...</div>
}
return (
<div>
{data.map(value => (
<div>{value}</div>
))}
</div>
)
}
Как мы видим, теперь нам не нужно повторно вызывать фетчинг данных в маунте и апдейте, у нас происходит это все в одном месте, useEffect
вносит немного другую парадигму о том, как нужно проектировать компонент.
В итоге мы проектируем так, как выглядил бы компонент в каждый момент времени.
а вместо useEffect можно вообще гейт и вынести логику из компонента 🤣