1. Проблемы, решаемые реактивными хуками
-
Функциональный компонент не может иметь собственного состояния. До хуков функциональные компоненты не имеют состояния, а состояние родительских компонентов получается через реквизиты, но хуки предоставляют useState для поддержания внутреннего состояния функциональных компонентов.
-
Жизненный цикл компонента нельзя отслеживать в функциональном компоненте. useEffect объединяет несколько функций жизненного цикла.
-
Жизненный цикл компонента класса более сложен (изменение от версии 15 к версии 16 велико).
-
Логику компонента класса сложно использовать повторно (HOC, реквизиты рендеринга).
2. Преимущества хуков по сравнению с классами
1. Написание более лаконичное
Возьмем для примера простейший счетчик:
компонент класса
class ExampleOfClass extends Component {
constructor(props) {
super(props)
this.state = {
count: 1
}
}
handleClick = () => {
let { count } = this.state
this.setState({
count: count+1
})
}
render() {
const { count } = this.state
return (
<div>
<p>you click { count }</p>
<button onClick={this.handleClick}>点击</button>
</div>
)
}
}
hooks
function ExampleOfHooks() {
const [count, setCount] = useState(0)
const handleClick = () => {
setCount(count + 1)
}
return (
<div>
<p>you click { count }</p>
<button onClick={handleClick}>点击</button>
</div>
)
}
Вы можете видеть, что код, использующий хуки, более лаконичен и понятен, чем код компонента класса.
2. Бизнес-коды более агрегированы
При использовании компонентов класса часто возникает ситуация, когда функция фигурирует в двух функциях жизненного цикла, поэтому о написании отдельно иногда можно забыть. Например:
let timer = null
componentDidMount() {
timer = setInterval(() => {
// ...
}, 1000)
}
// ...
componentWillUnmount() {
if (timer) clearInterval(timer)
}
Поскольку добавление таймера и очистка таймера — это две разные функции жизненного цикла, в середине может быть много других бизнес-кодов, поэтому вы можете забыть обнулить таймер.Если функция очистки таймера не добавлена при выгрузке компонента , это может быть постоянно вызывает такие проблемы, как утечка памяти и сетевые запросы.
Но использование хуков может сделать код более централизованным, удобным для нас в управлении и непростым для забвения:
useEffect(() => {
let timer = setInterval(() => {
// ...
}, 1000)
return () => {
if (timer) clearInterval(timer)
}
}, [//...])
3. Повторное использование логики удобно
Логическое повторное использование компонентов класса обычно осуществляется двумя способами: рендеринг реквизита и HOC. Реагирующие хуки предоставляют пользовательские хуки для повторного использования логики.
Ниже приведен пример логического повторного использования получения положения мыши на странице:
Повторное использование метода реквизита рендеринга компонента класса
import React, { Component } from 'react'
class MousePosition extends Component {
constructor(props) {
super(props)
this.state = {
x: 0,
y: 0
}
}
handleMouseMove = (e) => {
const { clientX, clientY } = e
this.setState({
x: clientX,
y: clientY
})
}
componentDidMount() {
document.addEventListener('mousemove', this.handleMouseMove)
}
componentWillUnmount() {
document.removeEventListener('mousemove', this.handleMouseMove)
}
render() {
const { children } = this.props
const { x, y } = this.state
return(
<div>
{
children({x, y})
}
</div>
)
}
}
// 使用
class Index extends Component {
constructor(props) {
super(props)
}
render() {
return (
<MousePosition>
{
({x, y}) => {
return (
<div>
<p>x:{x}, y: {y}</p>
</div>
)
}
}
</MousePosition>
)
}
}
export default Index
Повторное использование пользовательских хуков
import React, { useEffect, useState } from 'react'
function usePosition() {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
const handleMouseMove = (e) => {
const { clientX, clientY } = e
setX(clientX)
setY(clientY)
}
useEffect(() => {
document.addEventListener('mousemove', handleMouseMove)
return () => {
document.removeEventListener('mousemove', handleMouseMove)
}
})
return [
{x, y}
]
}
// 使用
function Index() {
const [position] = usePosition()
return(
<div>
<p>x:{position.x},y:{position.y}</p>
</div>
)
}
export default Index
Хорошо видно, что использование хуков удобнее для повторного использования логики, и логика при использовании становится понятнее.
3. Некоторые распространенные способы использования хуков в API
1, состояние использования
грамматика
const [value, setValue] = useState(0)
Этот синтаксис представляет собой структуру массива ES6, первое значение массива — объявленное состояние, а второе значение — функция изменения состояния.
Каждый кадр имеет независимое состояние
Лично я понимаю, что независимое состояние для каждого кадра реализуется методом замыкания.
function Example() {
const [val, setVal] = useState(0)
const timeoutFn = () => {
setTimeout(() => {
// 取得的值是点击按钮的状态,不是最新的状态
console.log(val)
}, 1000)
}
return (
<>
<p>{val}</p>
<button onClick={()=>setVal(val+1)}>+</button>
<button onClick={timeoutFn}>alertNumber</button>
</>
)
}
Когда состояние или реквизиты компонента обновляются, функциональный компонент будет вызываться и рендериться снова, и каждый рендеринг независим и имеет свои собственные независимые реквизиты и состояние, которые не повлияют на другие рендеринги.
2. использоватьЭффект
грамматика
useEffect(() => {
//handler function...
return () => {
// clean side effect
}
}, [//dep...])
useEffect получает функцию обратного вызова и зависимости, и функция обратного вызова будет выполняться при изменении зависимостей. useEffect аналогичен функциям жизненного цикла компонентов класса didMount, didUpdate и willUnmount.
будь осторожен
- useEffect является асинхронным и будет выполняться только после завершения рендеринга компонента.
- Функция обратного вызова useEffect может возвращать только функцию-обработчик, которая очищает побочный эффект или не возвращает
- Если зависимости, переданные в useEffect, представляют собой пустой массив, то функция внутри useEffect будет выполняться только один раз.
3. использоватьMemo, использоватьCallback
useMemo и useCallback в основном используются для уменьшения количества обновлений компонентов и оптимизации производительности компонентов.
- useMemo получает функцию обратного вызова и зависимости и повторно выполняет функцию обратного вызова только при изменении зависимостей.
- useCallback получает функцию обратного вызова и зависимости и возвращает запомненную версию функции обратного вызова, которая будет повторно запомнена только при повторном изменении зависимостей.
грамматика
const memoDate = useMemo(() => data, [//dep...])
const memoCb = useCallback(() => {//...}, [//dep...])
При оптимизации производительности компонента мы обычно используем React.PureComponent для компонентов класса.PureComponent выполнит сравнение денег в shouldUpdate, чтобы определить, нужно ли его обновлять; для функциональных компонентов мы обычно используем React.memo. Но при использовании хуков реакции, поскольку каждое обновление рендеринга независимо (генерируется новое состояние), даже если используется React.memo, он все равно будет повторно рендериться.
Например, в следующем сценарии после изменения значения имени дочернего компонента, поскольку родительский компонент будет генерировать новое значение при каждом обновлении (изменится функция addAge), дочерний компонент также будет перерендерен.
function Parent() {
const [name, setName] = useState('cc')
const [age, setAge] = useState(22)
const addAge = () => {
setAge(age + 1)
}
return (
<>
<p>父组件</p>
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>age: {age}</p>
<p>-------------------------</p>
<Child addAge={addAge} />
</>
)
}
const Child = memo((props) => {
const { addAge } = props
console.log('child component update')
return (
<>
<p>子组件</p>
<button onClick={addAge}>click</button>
</>
)
})
Оптимизация с помощью useCallback
function Parent() {
const [name, setName] = useState('cc')
const [age, setAge] = useState(22)
const addAge = useCallback(() => {
setAge(age + 1)
}, [age])
return (
<>
<p>父组件</p>
<input value={name} onChange={(e) => setName(e.target.value)} />
<p>age: {age}</p>
<p>-------------------------</p>
<Child addAge={addAge} />
</>
)
}
const Child = memo((props) => {
const { addAge } = props
console.log('child component update')
return (
<>
<p>子组件</p>
<button onClick={addAge}>click</button>
</>
)
})
Только изменения зависимости useCallback будут перегенерировать функцию запоминания. Так что при смене имени состояние addAge не изменится.
4. использоватьСсылка
useRef похож на react.createRef.
const node = useRef(initRef)
useRef возвращает изменяемый объект ref, текущее свойство которого инициализируется переданным параметром (initRef).
Закон о DOM
const node = useRef(null)
<input ref={node} />
Таким образом, к элементу DOM можно получить доступ через свойство node.current.
должен быть в курсеUSEREF созданные объекты остаются постоянными в течение всего жизненного цикла компонентов, что означает, что каждый раз, когда компонент функции повторно отображается, возвращаемый объект ref является одним и тем же (с помощью React.createRef ссылка создается повторно каждый раз при повторном отображении компонента).
5. использовать Редуктор
useReducer похож на редьюсер в Redux.
грамматика
const [state, dispatch] = useReducer(reducer, initstate)
useReducer передает функцию вычисления и инициализирует состояние, аналогично redux. Через возвращенное состояние мы можем получить доступ к состоянию, а состояние можно изменить с помощью отправки.
const initstate = 0;
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {number: state.number + 1};
case 'decrement':
return {number: state.number - 1};
default:
throw new Error();
}
}
function Counter(){
const [state, dispatch] = useReducer(reducer, initstate);
return (
<>
Count: {state.number}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
)
}
6. использовать контекст
С помощью useContext нам проще получить контекст, предоставляемый компонентами верхнего уровня.
родительский компонент
import React, { createContext, Children } from 'react'
import Child from './child'
export const MyContext = createContext()
export default function Parent() {
return (
<div>
<p>Parent</p>
<MyContext.Provider value={{name: 'cc', age: 21}}>
<Child />
</MyContext.Provider>
</div>
)
}
Подсборка
import React, { useContext } from 'react'
import { MyContext } from './parent'
export default function Parent() {
const data = useContext(MyContext) // 获取父组件提供的context
console.log(data)
return (
<div>
<p>Child</p>
</div>
)
}
Шаги для использования
- Родительский компонент создан и экспортирован
context:export const MyContext = createContext() - использование родительского компонента
providerа такжеvalueУкажите значение:<MyContext.provide value={{name: 'cc', age: 22}} /> - дочерний компонент импортировать родительский компонент
context:import { MyContext } from './parent' - Получите значение, предоставленное родительским компонентом:
const data = useContext(MyContext)
Однако в большинстве случаев мы не рекомендуем использоватьcontext, потому что это увеличит сцепление компонентов.
7. использовать эффект макета
useEffect будет выполняться после завершения всего рендеринга; useLayoutEffect будет выполняться после макета браузера, перед отрисовкой и будет запускать DOM; вы можете использовать его для чтения макета DOM и запускать повторный рендеринг синхронно.
export default function LayoutEffect() {
const [color, setColor] = useState('red')
useLayoutEffect(() => {
alert(color) // 会阻塞DOM的渲染
});
useEffect(() => {
alert(color) // 不会阻塞
})
return (
<>
<div id="myDiv" style={{ background: color }}>颜色</div>
<button onClick={() => setColor('red')}>红</button>
<button onClick={() => setColor('yellow')}>黄</button>
</>
)
}
В приведенном выше примере useLayoutEffect будет выполняться до рисования, а useEffect — после рисования.
Хуки позволяют функциональным компонентам иметь внутреннее состояние и жизненный цикл, использовать хуки, чтобы сделать код более кратким, пользовательские хуки облегчают повторное использование логики и избавляют от этой проблемы компонентов класса; но при использовании проблемы с хуками будут генерироваться некоторые замыкания, нужно использовать осторожно.
Справочная статья:nuggets.capable/post/684490…