предисловие
Реагистрационные крючки вводили новые функции в выпуске V16.8. На мой взгляд, использование реактивных крюков по сравнению с предыдущими компонентами класса имеют следующие преимущества:
- Код стал более читабельным.Исходная логика кода одной и той же функции разбита на разные функции жизненного цикла, что легко сделать разработчиков невыгодными для сопровождения и итерации.Через React Hooks можно агрегировать функциональные коды, что удобно для чтения и Обслуживание;
- Уровень дерева компонентов становится более мелким.В исходном коде мы часто используем реквизиты HOC/render и другие методы для повторного использования состояния компонентов, улучшения функций и т. д., что, несомненно, увеличивает количество слоев дерева компонентов и рендеринга.В React Hooks , все эти функции могут быть реализованы с помощью мощных пользовательских хуков;
В этой статье мы приводим примеры в соответствии со сценариями использования, чтобы помочь вам понять и умело использовать большинство функций React Hooks.
Я много работал над организацией в течение долгого времени, и я надеюсь вручную поставить лайк и поощрить ~
Адрес блога на github:GitHub.com/ревматизм123/…, Обобщить весь блог автора, приветствую внимание и звезды ~
1. Государственный хук
1. Основное использование
function State(){
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
2. Обновить
Обновление делится на следующие два способа, а именно прямое обновление и функциональное обновление.Разница между сценариями применения заключается в следующем:
- напрямую обновлять значения, не зависящие от старого состояния;
- Функциональные обновления зависят от значения старого состояния;
// 直接更新
setState(newCount);
// 函数式更新
setState(prevCount => prevCount - 1);
3. Реализуйте слияние
В отличие от метода setState в компоненте класса, useState не объединяет автоматически объект обновления, а напрямую заменяет его. Мы можем использовать функционал setState в сочетании с оператором распространения для достижения эффекта слияния и обновления объектов.
setState(prevState => {
// 也可以使用 Object.assign
return {...prevState, ...updatedValues};
});
4. Ленивая инициализация
Параметр initialState будет работать только при начальном рендеринге компонента и будет игнорироваться при последующих рендерингах. Сценарий применения такой: когда создание начального состояния затратно, например, его нужно получить путем сложных вычислений, тогда можно передать функцию, вычислить и вернуть начальное состояние в функцию, а эта функция вызывается только во время первоначальный рендеринг:
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
5. Некоторые ключевые моменты
(1) В отличие от this.setState в классе, Hook обновляет переменную состояния, всегда заменяя ее, а не объединяя; (2) Рекомендуется использовать несколько переменных состояния вместо одной переменной состояния, потому что логика замены состояния не является объединенной логикой и способствует разделению последующей связанной логики состояния; (3) Когда вызывается функция обновления State Hook и передается текущее состояние, React пропустит рендеринг дочерних компонентов и выполнение эффектов. (Реакция используетАлгоритм сравнения Object.isсравнить состояние. )
Во-вторых, эффект крюк
1. Основное использование
function Effect(){
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`You clicked ${count} times`);
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
2. Четкая операция
Во избежание утечек памяти функция очистки будет выполняться перед выгрузкой компонента; если компонент рендерится несколько раз (обычно), предыдущий эффект будет очищен до того, как будет выполнен следующий эффект, то есть функция возврата в сначала выполняется предыдущий эффект, а затем выполняется невозвратная функция в этом эффекте.
useEffect(() => {
const subscription = props.source.subscribe();
return () => {
// 清除订阅
subscription.unsubscribe();
};
});
3. Срок исполнения
В отличие от componentDidMount или componentDidUpdate, эффект, отправленный с помощью useEffect, не будет блокировать браузер для обновления экрана, что делает ваше приложение более отзывчивым; (componentDidMount или componentDidUpdate будут блокировать браузер для обновления экрана).
4. Оптимизация производительности
По умолчанию React будет откладывать вызов эффекта после каждого ожидания завершения рендеринга экрана браузером; но если определенные значения не изменились между повторными рендерингами, вы можете указать React пропустить вызов эффекта, передав Массив Его можно использовать как второй необязательный параметр useEffect: как показано ниже, если значение счетчика не меняется между двумя рендерингами, вызов эффекта будет пропущен после второго рендеринга;
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
5. Имитация компонентаDidMount
Если вы хотите запустить эффект только один раз (только когда компонент смонтирован и размонтирован), вы можете передать пустой массив ([ ]) в качестве второго параметра, как показано ниже, принцип тот же, что описан в четвертом точка оптимизации производительности;
useEffect(() => {
.....
}, []);
6. Лучшие практики
Трудно запомнить, какие реквизиты и состояния использует функция вне эффекта, поэтому обычно вам нужно объявить нужные функции внутри эффекта.
// bad,不推荐
function Example({ someProp }) {
function doSomething() {
console.log(someProp);
}
useEffect(() => {
doSomething();
}, []); // 🔴 这样不安全(它调用的 `doSomething` 函数使用了 `someProp`)
}
// good,推荐
function Example({ someProp }) {
useEffect(() => {
function doSomething() {
console.log(someProp);
}
doSomething();
}, [someProp]); // ✅ 安全(我们的 effect 仅用到了 `someProp`)
}
Если по какой-то причине вы не можете переместить функцию внутри эффекта, есть несколько других вариантов:
- Вы можете попробовать перенести эту функцию за пределы вашего компонента. Таким образом, функция определенно не зависит ни от каких реквизитов или состояний и не должна появляться в списке зависимостей;
- В крайнем случае вы можете добавить функцию в зависимость эффекта, но обернуть ее определение в хук useCallback. Это гарантирует, что он не изменится при рендеринге, если не изменятся его собственные зависимости;
Рекомендуется включитьeslint-plugin-react-hooks середина exhaustive-depsправило, это правило будет выдавать предупреждение при добавлении неправильных зависимостей и давать предложения по исправлению;
// 1、安装插件
npm i eslint-plugin-react-hooks --save-dev
// 2、eslint 配置
{
"plugins": [
// ...
"react-hooks"
],
"rules": {
// ...
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn"
}
}
7. Некоторые моменты
(1) UseEffect Hook можно рассматривать как комбинацию трех функций: componentDidMount, componentDidUpdate и componentWillUnmount; (2) В классах React-компонентов функция рендеринга не должна иметь никаких побочных эффектов, в общем-то здесь рано выполнять операции, а мы в основном хотим выполнять наши операции после того, как React обновит DOM.
В-третьих, используйте контекст
Многоуровневый подход к передаче данных, перед деревом компонентов, межуровневые компоненты-предки, когда вы хотите передать данные компонентам-внукам, в дополнение к слоям реквизита вниз через проход, мы также можем использовать справку React Context API. нам сделать это. Используйте примеры, показанные ниже (1) React Context API для создания контекста во внешнем компоненте.
import React from 'react';
const ThemeContext = React.createContext(0);
export default ThemeContext;
(2) Используйте Context.Provider для предоставления объекта Context, который может совместно использоваться дочерними компонентами.
import React, { useState } from 'react';
import ThemeContext from './ThemeContext';
import ContextComponent1 from './ContextComponent1';
function ContextPage () {
const [count, setCount] = useState(1);
return (
<div className="App">
<ThemeContext.Provider value={count}>
<ContextComponent1 />
</ThemeContext.Provider>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
export default ContextPage;
(3) Функция ловушки useContext() используется для введения объекта Context и получения его значения.
// 子组件,在子组件中使用孙组件
import React from 'react';
import ContextComponent2 from './ContextComponent2';
function ContextComponent () {
return (
<ContextComponent2 />
);
}
export default ContextComponent;
// 孙组件,在孙组件中使用 Context 对象值
import React, { useContext } from 'react';
import ThemeContext from './ThemeContext';
function ContextComponent () {
const value = useContext(ThemeContext);
return (
<div>useContext:{value}</div>
);
}
export default ContextComponent;
Четыре, используйте редьюсер
1. Основное использование
Сценарии, более подходящие, чем useState: например, обработка логики состояния сложна и содержит несколько подзначений, или следующее состояние зависит от предыдущего состояния и т. д. Примеры следующие:
import React, { useReducer } from 'react';
interface stateType {
count: number
}
interface actionType {
type: string
}
const initialState = { count: 0 };
const reducer = (state:stateType, action:actionType) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
};
const UseReducer = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div className="App">
<div>useReducer Count:{state.count}</div>
<button onClick={() => { dispatch({ type: 'decrement' }); }}>useReducer 减少</button>
<button onClick={() => { dispatch({ type: 'increment' }); }}>useReducer 增加</button>
</div>
);
};
export default UseReducer;
2. Ленивая инициализация
interface stateType {
count: number
}
interface actionType {
type: string,
paylod?: number
}
const initCount =0
const init = (initCount:number)=>{
return {count:initCount}
}
const reducer = (state:stateType, action:actionType)=>{
switch(action.type){
case 'increment':
return {count: state.count + 1}
case 'decrement':
return {count: state.count - 1}
case 'reset':
return init(action.paylod || 0)
default:
throw new Error();
}
}
const UseReducer = () => {
const [state, dispatch] = useReducer(reducer,initCount,init)
return (
<div className="App">
<div>useReducer Count:{state.count}</div>
<button onClick={()=>{dispatch({type:'decrement'})}}>useReducer 减少</button>
<button onClick={()=>{dispatch({type:'increment'})}}>useReducer 增加</button>
<button onClick={()=>{dispatch({type:'reset',paylod:10 })}}>useReducer 增加</button>
</div>
);
}
export default UseReducer;
5. Памятка
Как показано ниже, при повторном рендеринге родительского компонента дочерний компонент также повторно рендерится, даже если ни реквизиты, ни состояние дочернего не изменились.
import React, { memo, useState } from 'react';
// 子组件
const ChildComp = () => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<ChildComp/>
</div>
);
};
export default Parent;
Улучшение: мы можем использовать пакет memo для решения вышеуказанных проблем, но только для решения ситуации, когда родительский компонент не передает параметры дочернему компоненту, а родительский компонент передает дочернему компоненту параметры простого типа (такие как строка, число , boolean) и т. д.); если передаются сложные свойства, следует использовать useCallback (события обратного вызова) или useMemo (сложные свойства).
// 子组件
const ChildComp = () => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
6. использовать памятку
Предположим, что в следующем сценарии родительский компонент передает свойство информационного объекта при вызове дочернего компонента, и при нажатии кнопки родительского компонента обнаруживается, что консоль распечатает отображаемую информацию дочернего компонента.
import React, { memo, useState } from 'react';
// 子组件
const ChildComp = (info:{info:{name: string, age: number}}) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = { name, age };
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info}/>
</div>
);
};
export default Parent;
Проанализируйте причины:
- Нажмите кнопку родительского компонента, чтобы вызвать повторную визуализацию родительского компонента;
- Когда родительский компонент визуализируется, строка const info = { name, age } будет регенерировать новый объект, вызывая изменение значения свойства info, переданного дочернему компоненту, что, в свою очередь, вызывает повторную визуализацию дочернего компонента.
решить:
Используйте useMemo для переноса свойств объекта, useMemo имеет два параметра:
- Первый параметр — это функция, возвращаемый объект указывает на ту же ссылку, и новый объект не создается;
- Второй параметр — это массив, и функция первого параметра возвращает новый объект только при изменении переменных в массиве.
import React, { memo, useMemo, useState } from 'react';
// 子组件
const ChildComp = (info:{info:{name: string, age: number}}) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
// 使用 useMemo 将对象属性包一层
const info = useMemo(() => ({ name, age }), [name, age]);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info}/>
</div>
);
};
export default Parent;
Семь, воспользуйтесь обратным вызовом
Следуя примеру из главы 6, предположим, что вам нужно передать событие дочернему компоненту, как показано ниже, когда вы нажимаете кнопку родительского компонента, обнаруживается, что консоль выводит информацию о том, что дочерний компонент визуализируется. , указывая на то, что дочерний компонент был повторно визуализирован.
import React, { memo, useMemo, useState } from 'react';
// 子组件
const ChildComp = (props:any) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = useMemo(() => ({ name, age }), [name, age]);
const changeName = () => {
console.log('输出名称...');
};
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info} changeName={changeName}/>
</div>
);
};
export default Parent;
Проанализируйте причины:
- Нажатие кнопки родительского компонента изменяет значение переменной count в родительском компоненте (значение состояния родительского компонента), что, в свою очередь, вызывает повторную визуализацию родительского компонента;
- При повторном рендеринге родительского компонента функция changeName будет создана заново, то есть свойство changeName, переданное дочернему компоненту, изменилось, что привело к рендерингу дочернего компонента;
решить:Измените метод changeName родительского компонента и оберните его хуком-функцией useCallback Параметр useCallback аналогичен useMemo.
import React, { memo, useCallback, useMemo, useState } from 'react';
// 子组件
const ChildComp = (props:any) => {
console.log('ChildComp...');
return (<div>ChildComp...</div>);
};
const MemoChildComp = memo(ChildComp);
// 父组件
const Parent = () => {
const [count, setCount] = useState(0);
const [name] = useState('jack');
const [age] = useState(11);
const info = useMemo(() => ({ name, age }), [name, age]);
const changeName = useCallback(() => {
console.log('输出名称...');
}, []);
return (
<div className="App">
<div>hello world {count}</div>
<div onClick={() => { setCount(count => count + 1); }}>点击增加</div>
<MemoChildComp info={info} changeName={changeName}/>
</div>
);
};
export default Parent;
Восемь, используйтеRef
Два сценария использования useRef представлены следующим образом:
1. Укажите на элемент dom
Переменная, созданная с помощью useRef, указывает на элемент ввода, как показано ниже, и устанавливает фокус ввода после отображения страницы.
import React, { useRef, useEffect } from 'react';
const Page1 = () => {
const myRef = useRef<HTMLInputElement>(null);
useEffect(() => {
myRef?.current?.focus();
});
return (
<div>
<span>UseRef:</span>
<input ref={myRef} type="text"/>
</div>
);
};
export default Page1;
2. Храните переменные
Роль useRef в хуке реакции, как сказано на официальном сайте, это как переменная, похожая на эту, это как коробка, в которой можно хранить что угодно, createRef будет возвращать новую ссылку каждый раз, когда она будет отображаться, а useRef будет каждый раз возвращать новую ссылку Возвращать одну и ту же ссылку, как показано в следующем примере:
import React, { useRef, useEffect, useState } from 'react';
const Page1 = () => {
const myRef2 = useRef(0);
const [count, setCount] = useState(0)
useEffect(()=>{
myRef2.current = count;
});
function handleClick(){
setTimeout(()=>{
console.log(count); // 3
console.log(myRef2.current); // 6
},3000)
}
return (
<div>
<div onClick={()=> setCount(count+1)}>点击count</div>
<div onClick={()=> handleClick()}>查看</div>
</div>
);
}
export default Page1;
Девять, используйте ImperativeHandle
Сценарий использования: весь узел dom получается через ref, и только часть методов и свойств можно контролировать для предоставления через useImperativeHandle, а не весь узел dom.
Десять, используйтеLayoutEffect
Его подпись функции такая же, как с использованием использования, но она будет вызывать эффект синхронно ведь после всех изменений DOM, поэтому я не приведу пример здесь.
- useLayoutEffect выполняется одновременно с componentDidMount и componentDidUpdate обычного компонента класса;
- useEffect снова запустит планирование задачи после завершения этого обновления, то есть после выполнения метода пункта 1, и выполнит useEffect в следующем планировании задачи;
Суммировать
В этой статье мы приводим примеры в соответствии со сценариями использования, надеясь помочь вам понять и умело использовать большинство функций React Hooks.
Я много работал над организацией в течение долгого времени, и я надеюсь вручную поставить лайк и поощрить ~
Адрес блога на github:GitHub.com/ревматизм123/…, в котором собраны все блоги автора, приглашаем подписаться и отметиться ~