Что такое крючки
Хуки появились в React 16.8 впервые. Он позволяет вам использовать состояние и другие функции React без написания классов.
Зачем использовать хуки
Код читабелен и прост в обслуживании
1. Крючки используются в функциональных компонентах, нет необходимости поддерживать сложный жизненный цикл, не нужно беспокоиться об этой проблеме указания
Хуки расширяют возможности компонента «Функция», а компонент «Функция» также может поддерживать свое собственное состояние, не беспокоясь о проблеме, на которую указывает это, в процессе взаимодействия компонентов.
2. Лучшее повторное использование логики
По сравнению с текущим общим методом повторного использования кода реакции (компоненты более высокого порядка,render props) должен быть простым и понятным.Подробности см. в этой главе.Глава о пользовательских хуках
Повышение эффективности разработки
Давайте сравним различия в коде между одной и той же функцией, реализованной компонентом класса, и функциональной составляющей, реализованной с помощью хуков.
1. Версия компонента класса
import React from 'react';
class Person extends React.Component {
constructor(props) {
super(props);
this.state = {
username: "小明"
};
}
componentDidMount() {
console.log('组件挂载后要做的操作')
}
componentWillUnmount() {
console.log('组件卸载要做的操作')
}
componentDidUpdate(prevProps, prevState) {
if(prevState.username !== this.state.username) {
console.log('组件更新后的操作')
}
}
render() {
return (
<div>
<p>欢迎 {state.username}</p>
<input type="text" placeholder="input a username" onChange={(event) => this.setState({ username: event.target.value)})}></input>
</div>
);
}
}
2. Версия крючков
import React, {useState, useEffect} from 'react';
export const Person = () => {
const [name, setName] = useState("小明");
useEffect(() => {
console.log('组件挂载后要做的操作')
return () => {
console.log('组件卸载要做的操作')
}
}, []);
useEffect(() => {
console.log('组件更新后的操作')
}, [name]);
return (
<div>
<p>欢迎 {name}</p>
<input type="text" placeholder="input a username" onChange={(event) => setName( event.target.value)}></input>
</div>
)
}
Версия Hooks упрощает большую часть кода и может значительно повысить эффективность разработки после ознакомления с ней.
Как использовать хуки
Базовый API хуков
useState (фокус на освоении)
1. Параметры:
- Константа: определяется при инициализации компонента
import React, { useState } from 'react';
function Example() {
// 声明一个叫 "count" 的 state 变量,初始值为0,后续通过setCount改变它能让视图重新渲染
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
- Функция: функция будет выполняться только при запуске рендеринга.
// initialState 参数只会在组件的初始渲染中起作用,后续渲染时会被忽略。
// 如果初始 state 需要通过复杂计算获得,则可以传入一个函数,在函数中计算并返回初始的 state,
// 此函数只在初始渲染时被调用:
const [count, setCount] = useState(() => {
const initialCount = someExpensiveComputation(props);
return initialState;
})
2. Возвращаемое значение
Возвращаемое значение useState представляет собой массив длины 2. Первый элемент массива — это определенная переменная (имя задается вами), а второй элемент — функция, которая изменяет первый элемент (имя задается вами). ) Конкретный пример см. в приведенном выше коде.
useEffect (фокус на мастеринге)
Хук имеет два параметра.Первый параметр представляет собой функцию, которая содержит императивный код и может иметь побочные эффекты.Второй параметр представляет собой массив.Этот параметр определяет, будет ли выполняться функция, обернутая Эффектом, или нет.Если второй параметр не передан, Эффект будет выполняться каждый раз при обновлении компонента, что эквивалентно слиянию жизненных циклов componentDidMount и componentDidupdate в компоненте класса..
1. Основное использование
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// Similar to componentDidMount and componentDidUpdate:
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
2. Контролировать выполнение функции
Как и в приведенном выше коде, мы передаем второй параметр в useEffect.[count]
, так что он будет выполняться только при изменении счетчика
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
// 只有count改变时才会执行
useEffect(() => {
// Update the document title using the browser API
document.title = `You clicked ${count} times`;
},[count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
import React, { useEffect } from 'react';
function Example() {
// 组件挂载时只执行一次
useEffect(() => {
console.log("只执行一次,类似componentDidMount")
},[]);
return (
<div>只执行一次的Effect</div>
);
}
3. Побочные эффекты, которые необходимо устранить
Есть некоторые побочные эффекты, которые необходимо устранить. Например, подписка на внешние источники данных. В этом случае работа по очистке очень важна для предотвращения утечек памяти!
Пример 1 (очищается при каждом рендере):
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
Пример 2 (сбрасывается только при удалении компонента):
Но когда мы передаем во второй параметр пустой массив, только компонентыудалитьВ это время Effect выполнит операцию очистки.В это время useEffect эквивалентно слиянию componentDidMount и compinentWillUnmount компонента класса.
import React, { useState, useEffect } from 'react';
function FriendStatus(props) {
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
// Specify how to clean up after this effect:
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
},[]);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
Мы должны гибко использовать его в повседневном использовании, но постарайтесь использовать второй параметр для управления выполнением функции, что может оптимизировать производительность.
useContext (важно)
Хук получает объект контекста (возвращаемое значение React.createContext) и возвращает текущее значение контекста. Текущее значение контекста определяется значением свойства
1. Пример использования:
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 主题context
const ThemeContext = React.createContext(themes.light);
function App() {
// 这里的value值改变,useContext包裹的值也会改变
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton() {
// 上层最近的Provider的value属性的值
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
2. Компоненты класса реализуют ту же логику, см.реагировать на официальную документацию - Контекст
Простой пример:
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的 theme 创建一个 context(“light”为默认值)。
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render() {
return <Button theme={this.context} />;
}
}
useReducer (важно)
Альтернатива useState. Он принимает редюсер вида (состояние, действие) => newState и возвращает текущее состояние и его метод отправки (очень похоже на редукс).
const [state, dispatch] = useReducer(reducer, initialArg, init);
В некоторых сценариях useReducer больше подходит, чем useState, например, логика состояния более сложная и содержит несколько подзначений, или следующее состояние зависит от предыдущего состояния и т. д. Кроме того, использование useReducer также может оптимизировать производительность компонентов, запускающих глубокие обновления, поскольку вы можете передавать диспетчеризацию дочерним компонентам вместо функций обратного вызова.
параметр:
- Первый параметр — это чистая функция редуктора.
- Второй параметр - начальное состояние
- Третий параметр может изменить начальное состояние и установить начальное состояние в init(initialArg).
1. Основное использование
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
useCallback (фокус на освоении)
Передайте встроенную функцию обратного вызова и массив зависимостей в качестве параметров для useCallback, и он вернет запомненную версию функции обратного вызова,Функция обратного вызова обновляется только при изменении зависимости.
- Общие сценарии приложений: родительские компоненты передают функции обратного вызова дочерним компонентам (ноОфициальный представитель React не рекомендует этот метод, официально рекомендуется использовать хук useReducer, чтобы избежать этой формы путем передачи отправки.официальное объяснение)
- Пример:
import React, { useEffect, useState, useCallback } from 'react';
// 子组件
function Son({callback}) {
renturn (
<a onClick={()=>callback("小红")}>点击切换姓名</a>
)
}
// 父组件
function Parent() {
const [name,setName] = useState("")
useEffect(() => {
console.log("获取数据并更新state")
setName("小明")
},[]);
const callback = useCallback(name => {
setName(name);
}, []);
return (
<>
<Son callback={callback} />;
name:{name}
<>
)
}
useMemo (сосредоточьтесь на мастеринге)
useCallback(fn, deps) эквивалентно useMemo(() => fn, deps).
Передайте функцию «создать» и массив зависимостей в качестве аргументов для useMemo, который будет пересчитывать мемоизированное значение только при изменении зависимости.Эта оптимизация помогает избежать дорогостоящих вычислений при каждом рендеринге..
Если массив зависимостей не предоставлен, useMemo будет вычислять новые значения при каждом рендеринге.
Вы можете использовать useMemo для оптимизации производительности, но не воспринимайте это как семантическую гарантию!
Сценарии применения:
- Храните дорогостоящие вычисления
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
- Пропустить дорогостоящий повторный рендеринг дочерних узлов
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
useRef (важно)
useRef возвращаетПеременнаяref объект, которыйcurrentСвойства инициализируются переданным параметром (initialValue).Возвращенный объект ref остается неизменным в течение всего времени жизни компонента..
const refContainer = useRef(initialValue);
используемые сцены:
- Доступ к дочернему компоненту dom
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
- сохранить переменную экземпляра
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// ...
return <div>使用useRef存储实例变量</div>
}
useImperativeHandle (обычно не используется)
useImperativeHandle(ref, createHandle, [deps])
useImperativeHandle позволяет настроить значение экземпляра, предоставляемое родительскому компоненту при использовании ref. В большинстве случаев следует избегать императивного кода, такого как ref. useImperativeHandle следует использовать с forwardRef:
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
В этом примере рендеринг<FancyInput ref={inputRef} />
Родительский компонент может вызыватьinputRef.current.focus()
.
useLayoutEffect (обычно не используется)
Сигнатура его функции такая же, как и у useEffect, и тот же метод, но он будет вызывать эффект синхронно после всех изменений DOM. Вы можете использовать его для чтения макета DOM и синхронного повторного рендеринга. Расписание обновления внутри useLayoutEffect будет обновляться синхронно перед тем, как браузер выполнит рисование.
По возможности используйте стандартный useEffect, чтобы избежать блокировки визуальных обновлений.
- Разница между useEffect и componentDidMount и componentDidUpdate заключается в том, что после того, как браузер завершит макет и отрисовку, функция, переданная в useEffect, будет вызываться с задержкой.
- useLayoutEffect совпадает с временем вызова componentDidMount и componentDidUpdate.
useDebugValue (обычно не используется)
На этапе разработки используйте, конкретную ссылку на использованиеофициальная документация
Крюк продвинутый
Пользовательские крючки
Настроив Hook, вы можетеИзвлечение многократно используемой логики из нескольких компонентов, чтобы добиться повторного использования логики.
Пример (следующий пример взят изБлог Руан Ифэн):
const Person = ({ personId }) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${personId}/`)
.then(response => response.json())
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId])
if (loading === true) {
return <p>Loading ...</p>
}
return <div>
<p>You're viewing: {person.name}</p>
<p>Height: {person.height}</p>
<p>Mass: {person.mass}</p>
</div>
}
Мы извлекаем логику получения человека из приведенного выше кода, чтобы облегчить вызов других подобных компонентов.
const usePerson = (personId) => {
const [loading, setLoading] = useState(true);
const [person, setPerson] = useState({});
useEffect(() => {
setLoading(true);
fetch(`https://swapi.co/api/people/${personId}/`)
.then(response => response.json())
.then(data => {
setPerson(data);
setLoading(false);
});
}, [personId]);
return [loading, person];
};
UsePerson в приведенном выше коде — это настраиваемый хук, и мы можем использовать его в других компонентах, например:
const Person = ({ personId }) => {
const [loading, person] = usePerson(personId);
if (loading === true) {
return <p>Loading ...</p>;
}
return (
<div>
<p>You're viewing: {person.name}</p>
<p>Height: {person.height}</p>
<p>Mass: {person.mass}</p>
</div>
);
};
Самостоятельная реализация нескольких распространенных пользовательских хуков
- useFetch (простая версия): получить данные интерфейса
import { useState, useEffect} from 'react';
import fetch from 'fetch';
/**
* @param {String} url
* @param {Object} initState
*/
const useFetch_0 = (url, initState) => {
const [isLoading, setIsLoading] = useState(false);
const [data, setDate] = useState(initState);
const [isError, setIsError] = useState(false);
useEffect(() => {
const fetchData = async () =>{
setIsLoading(true);
try {
const res = await fetch(url);
setDate(res);
} catch (error) {
setIsError(true);
}
setIsLoading(false);
}
fetchData();
}, [url]);
return [
data,
isLoading,
isError,
];
}
export default useFetch_0;
На родительской странице используются:const [data,isLoading,isError] = useFetch(url,initState)
- usePrevious: Получить реквизит и состояние предыдущего раунда
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
// 使用
function Counter() {
const [count, setCount] = useState(0);
const prevCount = usePrevious(count);
return <h1>Now: {count}, before: {prevCount}</h1>;
}
Пользовательские хуки премиум-класса сторонних производителей
На github уже есть много качественных кастомных хуков, адрес ссылки:GitHub.com/Re hooks/awe…
Пример нестандартных хуков
useDeepCompareEffect
import React from 'react';
import { useDeepCompareEffect } from 'use-deep-compare';
function App({ object, array }) {
useDeepCompareEffect(() => {
// do something significant here
return () => {
// return to clean up that significant thing
};
}, [object, array]);
return <div>{/* render significant thing */}</div>;
}
useDeepCompareCallback
import React from 'react';
import { useDeepCompareCallback } from 'use-deep-compare';
function App({ object, array }) {
const callback = useDeepCompareCallback(() => {
// do something significant here
}, [object, array]);
return <div>{/* render significant thing */}</div>;
}
useDeepCompareMemo
import React from 'react';
import { useDeepCompareMemo } from 'use-deep-compare';
function App({ object, array }) {
const memoized = useDeepCompareMemo(() => {
// do something significant here
}, [object, array]);
return <div>{/* render significant thing */}</div>;
}
import React, { useState } from 'react';
import { useDebounce } from 'use-debounce';
export default function Input() {
const [text, setText] = useState('Hello');
const [value] = useDebounce(text, 1000);
return (
<div>
<input
defaultValue={'Hello'}
onChange={(e) => {
setText(e.target.value);
}}
/>
<p>Actual value: {text}</p>
<p>Debounce value: {value}</p>
</div>
);
}
const data = useAsyncMemo(doAPIRequest, [])
Используйте хуки для реализации общего жизненного цикла компонентов класса.
- componentDidMount
useEffect(()=>{
// do something
},[])
- componentDidUpdate
useEffect(()=>{
// do something
})
- componentWillUnmount
useEffect(()=>{
return ()=> {
// do something
}
},[])
- getDerivedStateFromProps:Официальный учебник
function ScrollView({row}) {
let [isScrollingDown, setIsScrollingDown] = useState(false);
let [prevRow, setPrevRow] = useState(null);
if (row !== prevRow) {
// Row 自上次渲染以来发生过改变。更新 isScrollingDown。
setIsScrollingDown(prevRow !== null && row > prevRow);
setPrevRow(row);
}
return `Scrolling down: ${isScrollingDown}`;
}
- shouldComponentUpdate
Вы можете использовать useMemo, если это не предполагает сравнение внутреннего состояния компонента, рекомендуется использовать memo
function Parent({ a, b }) {
// Only re-rendered if `a` changes:
const child1 = useMemo(() => <Child1 a={a} />, [a]);
// Only re-rendered if `b` changes:
const child2 = useMemo(() => <Child2 b={b} />, [b]);
return (
<>
{child1}
{child2}
</>
)
}
Часто задаваемые вопросы по хукам
Большинство распространенных проблем отражено в приведенном выше коде, пожалуйста, обратитесь к другим проблемам.Модуль «Вопросы официальной документации»
Крючки Примечания
- Используйте хуки только на верхнем уровне
- Вызывайте хуки только в функциях React
- Подробные правила смотрите в официальной документацииправило крючков
Суммировать
- useState и useEffect могут охватывать большинство бизнес-сценариев.
- Сложные компоненты используют useReducer вместо useState.
- Если useState и useEffect не соответствуют бизнес-требованиям, используйте useContext, useRef или сторонние настраиваемые перехватчики для решения проблемы.
- useMemo и useCallback используются для оптимизации производительности, если они не используются, код должен работать корректно