Хуки появились в React 16.8 впервые. Это позволяет вам использовать состояние и другие функции React без написания классов.
Из этого предложения на официальном сайте мы можем ясно понять, чтоHookДобавлены функциональные компонентыstateИспользование , в прошлом, функциональные компоненты не могли иметь своего состояния, только черезpropsтак же какcontextсделать свой собственныйUI, а в бизнес-логике некоторые сценарии должны использоватьstate, то мы можем определить функциональные компоненты только какclassкомпоненты. а теперь черезHook, мы можем легко поддерживать наше состояние в функциональных компонентах, не меняя их наclassкомпоненты.
React HooksПроблема, которую необходимо решить, — это совместное использование состояния.Под общим состоянием здесь понимается только повторное использование логики состояния, а не совместное использование данных. мы знаем, что вReact HooksРаньше для решения проблемы повторного использования логики состояния мы обычно использовалиhigher-order componentsа такжеrender-props, то если есть оба решения, то почемуReactРазработчики также должны представитьReact Hook? дляhigher-order componentsа такжеrender-props,React HookГде преимущества?
Пример хука реакции
Давайте сначала посмотримReactофициально даноReact Hookизdemo
import { useState } from 'React';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Посмотрим еще разReact HookЕсли да, то как добиться
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
Видно, что вReact Hookсередина,class ExampleКомпонент становится функциональным компонентом, но функциональный компонент имеет свое собственное состояние и также может обновлять свое состояние. Все это благодаряuseStateэтоHook,useStateвернет пару значений: текущее состояние и функцию, позволяющую обновить его, которую можно вызвать в обработчике событий или в другом месте. это что-то вродеclassкомпонентthis.setState, но новую не поставитstateи старыйstateобъединить
ReactРешения для повторного использования логики состояния
Hookэто еще одно решение для повторного использования логики состояния,ReactРазработчики постоянно предлагали и улучшали схему повторного использования логики состояния, начиная сMixinк компонентам более высокого порядка, чтобыRender Propsдо сих порHook, давайте кратко рассмотрим предыдущее решение
Mixinмодель
существуетReactНа начальном этапе было предложеноMixinШаблон для повторного использования логики между компонентами. существуетJavascript, мы можем поставитьMixinНаследование рассматривается как способ расширения функциональности коллекции.Каждый новый объект, который мы определяем, имеет прототип, от которого он может наследовать дополнительные свойства.Прототипы могут быть унаследованы от других объектов, но что более важно, возможность определять свойства для любого количества объектов.Мы может воспользоваться этим фактом для облегчения функционального повторного использования.
ReactсерединаmixinВ основном используется в двух совершенно не связанных компонентах, есть набор в принципе схожих функций, мы можем извлечь его черезmixinспособ ввода, чтобы добиться повторного использования кода. Например, в разных компонентах компоненты нужно обновлять время от времени, мы можем создатьsetInterval()для достижения этой функции, и когда компонент будет уничтожен, нам нужно выгрузить эту функцию. Таким образом, можно создать простойmixin, предоставляя простойsetInterval()функция, которая автоматически очищается при уничтожении компонента.
var SetIntervalMixin = {
componentWillMount: function() {
this.intervals = [];
},
setInterval: function() {
this.intervals.push(setInterval.apply(null, arguments));
},
componentWillUnmount: function() {
this.intervals.forEach(clearInterval);
}
};
var createReactClass = require('create-React-class');
var TickTock = createReactClass({
mixins: [SetIntervalMixin], // 使用 mixin
getInitialState: function() {
return {seconds: 0};
},
componentDidMount: function() {
this.setInterval(this.tick, 1000); // 调用 mixin 上的方法
},
tick: function() {
this.setState({seconds: this.state.seconds + 1});
},
render: function() {
return (
<p>
React has been running for {this.state.seconds} seconds.
</p>
);
}
});
ReactDOM.render(
<TickTock />,
document.getElementById('example')
);
mixinНедостатки
- разные
mixinМожет быть взаимозависимым, и связь слишком сильная, что приводит к высоким затратам на техническое обслуживание в более поздний период. -
mixinИмена в списке могут конфликтовать и не могут использовать одно и то же имя.mixin -
mixinДаже если они начинаются с простого, со временем они могут усложняться лавинообразно, поскольку бизнес-сценарии умножаются.
Конкретные недостатки смотрите по этой ссылкеМиксины - это бич
потому чтоmixinиз этих недостатков существуют, вReactустарело вmixinрежим повторного использования кода,ReactВместо этого полностью рекомендуется использовать компоненты более высокого порядка.mixinрежим, покаES6сама не содержитmixinслужба поддержки. Поэтому, когда выReactиспользуется вES6 classне будет поддерживатьmixins.
компоненты более высокого порядка
компоненты более высокого порядка
(HOC)даReactУсовершенствованный метод повторного использования логики компонентов в .HOCне самReact APIчастьReactШаблоны проектирования, сформированные путем объединения характеристик
Расширенные компоненты неReactкоторый предоставилAPI, ноReactТехнику использования компонентов более высокого порядка можно рассматривать как шаблон декоратора (Decorator Pattern)существуетReactреализация. Шаблон декоратора: для динамического назначения обязанностей объектам и расширения функциональности декораторы предоставляют более гибкую альтернативу наследованию.
В частности, компонент более высокого порядка — это функция, параметр которой является компонентом, а возвращаемое значение — новым компонентом.
Компонент преобразует свойства в пользовательский интерфейс, а компонент более высокого порядка преобразует компонент в другой компонент.
Мы можем динамически добавлять функции печати журнала к другим компонентам через компоненты более высокого порядка, не затрагивая функции исходных компонентов.
function logProps(WrappedComponent) {
return class extends React.Component {
componentWillReceiveProps(nextProps) {
console.log('Current props: ', this.props);
console.log('Next props: ', nextProps);
}
render() {
return <WrappedComponent {...this.props} />;
}
}
}
Render Propss
Термин «реквизиты рендеринга» относится к простой технике совместного использования кода между компонентами React с использованием реквизита, значением которого является функция.
Компоненты с Render Props принимают функцию, которая возвращает элемент React, и вызывают ее вместо реализации собственной логики рендеринга.
Ниже мы предоставляемpropиз<Mouse>компоненты, которые могут динамически решать, что нужно визуализировать, чтобы<Mouse>Логика компонента и повторное использование состояния без изменения структуры его рендеринга.
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<Mouse render={mouse => (
)}/>
</div>
);
}
}
Однако обычно мы говоримRender PropsЭто потому, что шаблон называетсяRender Props, а не потому, что его нужно использоватьrenderправильноpropНазови это. Мы также можем выразить это
<Mouse>
{mouse => (
<Cat mouse={mouse} />
)}
</Mouse>
Мотивация React Hook
React HookЭто еще одно совершенно новое решение, предложенное официальным сайтом.React HookПрежде давайте посмотримReact Hookмотивация
- Повторное использование логики состояния между компонентами сложно
- Сложные компоненты становятся трудными для понимания
- Трудно понять
class
Вот мое понимание этих трех мотивов:
Повторное использование логики состояния между компонентами сложно, прежде чем мы передадим компонент более высокого порядка (Higher-Order Components) и свойства рендеринга (Render Propss) для решения сложной проблемы повторного использования логики состояния. Многие библиотеки используют эти шаблоны для повторного использования логики состояния, такой как наша обычно используемаяredux,React Router. Компоненты более высокого порядка и свойства рендеринга являются вложенными и совместно используемыми компонентами слой за слоем посредством комбинирования, что значительно увеличит иерархическую взаимосвязь нашего кода, что приведет к чрезмерно преувеличенной вложенности. отReactизdevtoolМы можем ясно видеть уровень вложенности, вызванный использованием этих двух режимов.
Сложные компоненты становятся трудными для понимания, В постоянно меняющихся бизнес-требованиях компоненты будут постепенно заполняться логикой состояния и побочными эффектами, и каждый жизненный цикл часто содержит некоторую неуместную логику. Мы обычно пишем код по единому принципу функций, функция обычно обрабатывает только одну вещь, но в функции-хуке жизненного цикла она обычно делает много вещей одновременно. Например, когда нам нужноcomponentDidMountинициировано вajaxВ этом жизненном цикле прописывается запрос на получение данных, а иногда и привязка события, и даже иногда нужноcomponentWillReceivePropsДанные следуютcomponentDidMountТакая же обработка.
Код, который связан друг с другом и нуждается в изменении, разделяется, а код, который совершенно не связан, объединяется в одном методе. Это чревато ошибками и приводит к несоответствиям в логике.
непонятный класс, лично чувствую, используяclassКомпоненты все еще в порядке, если вы понимаетеclassизthisУказав на проблему привязки, на самом деле начать работу несложно. Поймите, что это неReactсвоеобразное поведение; это на самом деле связано сКак работают функции JavaScriptСвязанный. Так что просто поймиJSКак работает функция, на самом делеthisПривязка ни о чем. просто иногда для гарантииthisДело верное, мы обычно пишем много кода для привязкиthisЕсли вы забудете привязать, будет разныеbug. связыватьthisметод:
1.this.handleClick = this.handleClick.bind(this);
2.<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
Поэтому для решения вышеуказанных проблемReact Hookбыл воспитан
использование хука состояния
Давайте вернемся к коду и посмотрим, как определить его в функциональном компоненте.state
import React, { useState } from 'React';
const [count, setCount] = useState(0);
-
useStateчто ты сделалМы видим, что в этой функции мы передаем
useStateопределяет «переменную состояния», которая связана сclassвнутриthis.stateобеспечивает точно такую же функциональность. Эквивалентно следующему кодуclass Example extends React.Component { constructor(props) { super(props); this.state = { count: 0 }; } -
useStateпараметрВ коде мы передаем
0так какuseStateпараметр, значение этого параметра будет рассматриваться какcountПервоначальный значение. Конечно, этот параметр не ограничивается передачей чисел и строк, вы можете передать объект как начальный.state. еслиstateНужно хранить значения нескольких переменных, а затем вызывать несколько разuseStateТолько что -
useStateвозвращаемое значениеВозвращаемое значение: текущее
stateи обновитьstateфункция, аналогичнаяclassвthis.state.countа такжеthis.setStateАналогично, с той лишь разницей, что вам нужно получить их парами. Видеть[count, setCount]Легко понять, что это метод записи деконструированного массива ES6. Эквивалентно следующему кодуlet _useState = useState(0);// 返回一个有两个元素的数组 let count = _useState[0];// 数组里的第一个值 let setCount = _useState[1];// 数组里的第二个值
Чтение значения состояния
Просто используйте переменные
предыдущее написание
<p>You clicked {this.state.count} times</p>
написать сейчас
<p>You clicked {count} times</p>
обновить состояние
пройти черезsetCountобновление функции
предыдущее написание
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
написать сейчас
<button onClick={() => setCount(count + 1)}>
Click me
</button>
здесьsetCountПолученный параметр представляет собой измененное новое значение состояния.
Объявить несколько переменных состояния
Мы можем использовать несколько раз в компонентеstate Hookобъявить несколькоstateПеременная
function ExampleWithManyStates() {
// 声明多个 state 变量!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
Реакция предполагает, что когда вы звонили несколько разuseState, вы можете гарантировать, что порядок, в котором они называются, одинаково каждый раз, когда вы визуете
ЗачемReactПонятно, что порядок вызова при каждом их отображении неизменен.Hookкритический вопрос
Правила хука
HookСуть в томJavaScriptФункция, но когда вы используете его, вам нужно следовать двум правилам. а такжеReactНеобходимо соблюдать эти два правила, иначе будут исключенияbug
- Использовать только на верхнем уровне
Hook
Не вызывайте хуки в циклах, условных выражениях или вложенных функциях,убедитесь, что вы всегдаReactВерхний уровень функции вызывает их
- только в
Reactвызов функцииHook
Не вызывайте хуки в обычных функциях JavaScript.
Причина этих двух правил в том, что мы можем использовать несколькоState HookилиEffect Hook,Reactполагаться наHookпорядок звонков, чтобы узнать, какойstateчто соответствуетuseState
function Form() {
const [name1, setName1] = useState('Arzh1');
const [name2, setName2] = useState('Arzh2');
const [name3, setName3] = useState('Arzh3');
// ...
}
// ------------
// 首次渲染
// ------------
useState('Arzh1') // 1. 使用 'Arzh1' 初始化变量名为 name1 的 state
useState('Arzh2') // 2. 使用 'Arzh2' 初始化变量名为 name2 的 state
useEffect('Arzh3') // 3. 使用 'Arzh3' 初始化变量名为 name3 的 state
// -------------
// 二次渲染
// -------------
useState('Arzh1') // 1. 读取变量名为 name1 的 state(参数被忽略)
useState('Arzh2') // 2. 读取变量名为 name2 的 state(参数被忽略)
useEffect('Arzh3') // 3. 读取变量名为 name3 的 state(参数被忽略)
если мы нарушимReactправила, использующие условный рендеринг
if (name !== '') {
const [name2, setName2] = useState('Arzh2');
}
Допустим, в первый раз(name !== '')дляtrue, выполнить этоHook, второй рендер(name !== '')дляfalse, не делайте этогоHook,ТакHookПорядок вызовов изменится, в результате чегоbug
useState('Arzh1') // 1. 读取变量名为 name1 的 state
//useState('Arzh2') // 2. Hook被忽略
useEffect('Arzh3') // 3. 读取变量名为 name2(之前为name3) 的 state
Reactне знаю второйuseState изHookчто надо вернуть.Reactподумал бы, что в компоненте второйHookВызов подобен последнему рендерингу, соответствующемуarzh2 изuseState, но не так. так вот почемуReactОбязательныйHookИспользование должно следовать этим двум правилам, и мы можем использоватьeslint-plugin-React-Hooksдля обеспечения соблюдения ограничений
Использование хука эффекта
Мы добавляем в приведенный выше кодEffect Hookиспользование, добавление побочных эффектов к функциональным компонентам, изменение заголовка веб-страницы
useEffect(() => {
document.title = `You clicked ${count} times`;
});
Если вы знакомы с функциями жизненного цикла класса React, вы можете поместить
useEffectКрюк какcomponentDidMount,componentDidUpdateа такжеcomponentWillUnmountСочетание этих трех функций.
То есть мы можем полностьюuseEffectчтобы заменить эти три функции спасательного крюка
Давайте рассмотрим сценарии, которые обычно требуют побочных эффектов, таких как отправка запросов, ручные измененияdom, ведение журнала и т. д. Обычно мы будемdomРендеринг завершен и последующийdomПри повторном обновлении вызовите нашу побочную операцию. Мы можем посмотреть на реализацию предыдущего жизненного цикла
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
Это то, что мы упоминали вышеReact HookОдин из вторых проблемных источников мотивации, необходимость вызывать один и тот же код как при первом рендере, так и при последующих рендерах.
EffectПо умолчанию после первого рендерингаа такжеОн будет выполняться после каждого обновления, что избавит нас от необходимости думать о том,componentDidMountещеcomponentDidUpdateпри выполнении,Просто нужно понимать, что Эффект выполняется после рендеринга компонента.
явные побочные эффекты
Иногда нам нужно очистить некоторые побочные эффекты, например, если у нас есть требование опросить сервер, чтобы запросить последний статус, нам нужно очистить операцию опроса при удалении.
componentDidMount() {
this.pollingNewStatus()
}
componentWillUnmount() {
this.unPollingNewStatus()
}
мы можем использоватьEffectчтобы устранить эти побочные эффекты, простоEffectвернуть функцию
useEffect(() => {
pollingNewStatus()
//告诉React在每次渲染之前都先执行cleanup()
return function cleanup() {
unPollingNewStatus()
};
});
Очевидная разница в том, чтоuseEffectНа самом деле он будет выполняться перед каждым рендерингомcleanup(),а такжеcomponentWillUnmountбудет выполняться только один раз.
Оптимизация производительности эффектов
useEffectФактически, он выполняется при каждом обновлении, что в некоторых случаях может вызвать проблемы с производительностью. тогда мы можем пропуститьEffectВыполнить оптимизацию производительности. существуетclassкомпонент, мы можем передатьcomponentDidUpdateдобавить пару вprevProps или prevStateСравнительно-логическое решение
componentDidUpdate(prevProps, prevState) {
if (prevState.count !== this.state.count) {
document.title = `You clicked ${this.state.count} times`;
}
}
существуетEffect, мы можем добавитьEffectВторой параметр , если нет изменений, пропустить обновление
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]); // 仅在 count 更改时更新
Другие крючки
Из-за недостатка места он не будет здесь расширяться, если вам интересно, вы можете просмотреть его на своем официальном сайте.