предисловие
Прежде всего, приглашаю всех подписаться на меняБлог на гитхабе, это можно расценивать как небольшое поощрение для меня.В конце концов, я не могу получить деньги, чтобы что-то написать.Я могу придерживаться своего собственного энтузиазма и всеобщего поощрения.Я надеюсь, что все обратят больше внимания! Функция Hooks была добавлена в React 16.8, а модуль Hooks был добавлен в официальную документацию React, чтобы представить новые функции.Видно, что React придает большое значение Hooks.Если вы все еще не знаете, что такое Hooks, настоятельно рекомендуется их понять, ведь за этим может стать будущее React.
источник
В React всегда было два способа создания компонентов: функциональные компоненты и классовые компоненты. Функциональный компонент — это обычная функция JavaScript, которая принимаетprops
объект и возвращает элемент React. На мой взгляд, функциональные компоненты больше соответствуют идее React, представления, управляемые данными, без каких-либо побочных эффектов и состояний. В приложениях функциональные компоненты обычно используются только для очень простых компонентов, и вы обнаружите, что по мере роста и изменений вашего бизнеса компоненты могут содержать состояние и другие побочные эффекты, поэтому вам придется переписать предыдущие функциональные компоненты для компонентов класса. Но часто все оказывается не так просто, классовые компоненты не так красивы, как мы себе представляли, кроме лишнего увеличения нагрузки есть еще разные проблемы.
Во-первых, для компонентов класса очень проблематично совместно использовать логику состояния. Например, мы заимствуем сцену из официального документа, компонент FriendStatus используется для отображения того, находится ли пользователь в списке друзей в сети.
class FriendStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
if (this.state.isOnline === null) {
return 'Loading...';
}
return this.state.isOnline ? 'Online' : 'Offline';
}
}
Вышеупомянутый компонент FriendStatus будет активно подписываться на статус пользователя при его создании и отписываться от статуса при его удалении, чтобы предотвратить утечку памяти. Предположим, есть еще один компонент, который также должен подписываться на онлайн-статус пользователя.Если мы хотим повторно использовать эту логику, мы обычно используемrender props
И компоненты более высокого порядка для повторного использования логики состояния.
// 采用render props的方式复用状态逻辑
class OnlineStatus extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
const {isOnline } = this.state;
return this.props.children({isOnline})
}
}
class FriendStatus extends React.Component{
render(){
return (
<OnlineStatus friend={this.props.friend}>
{
({isOnline}) => {
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
}
</OnlineStatus>
);
}
}
// 采用高阶组件的方式复用状态逻辑
function withSubscription(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
this.state = { isOnline: null };
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
render() {
return <WrappedComponent isOnline={this.state.isOnline}/>
}
}
}
const FriendStatus = withSubscription(({isOnline}) => {
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
})
Когда вышеупомянутые два состояния мультиплексируют логический способ реконструировать не только многоразмерную сборку и devtools. Просмотреть иерархию компонентов, вы найдете компонентную иерархию более глубоко, когда мультиплексирование логического состояния тоже попадет в вложенную монтажу чехол (обертка ада) является. Видимый над двумя методами не может решить проблему состояния идеального логического повторного использования.
Мало того, что по мере усложнения бизнес-логики в компоненте класса сложность обслуживания будет постепенно возрастать, поскольку логика состояния будет разделена на разные функции жизненного цикла, например, логика состояния подписки находится вcomponentDidMount
, логика отписки находится вcomponentWillUnmount
В , код связанной логики отделен друг от друга, а код несвязанной логики может быть сконцентрирован вместе, и в целом это не способствует сопровождению. И по сравнению с функциональными компонентами, классовые компоненты более сложны в изучении, вам нужно все время быть начеку.this
Ловушки в компонентах, никогда не забывайте привязывать обработчики событийthis
. При всем этом кажется, что функциональные компоненты по-прежнему обладают уникальными преимуществами.
Hooks
Функциональные компоненты всегда не хватало характеристик классовых компонентов, таких как состояние, жизненный цикл и т. Д., И появление крючков состоит в том, чтобы позволить функциональным компонентам иметь характеристики компонентов класса. Официальное определение:
Хуки — это функции, которые позволяют «подключаться» к состоянию реакции и функциям жизненного цикла из функциональных компонентов.
Чтобы функциональный компонент имел характеристики компонента класса, мы должны сначала реализовать состояниеstate
логика.
State: useState useReducer
useState
Это самый простой и часто используемый хук, предоставляемый React, который в основном используется для определения локального состояния.В качестве примера возьмем простой счетчик:
import React, { useState } from 'react'
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<span>{count}</span>
<button onClick={()=> setCount(count + 1)}>+</button>
<button onClick={() => setCount((count) => count - 1)}>-</button>
</div>
);
}
useState
Может использоваться для определения состояния.В отличие от состояния, состояние может быть не только объектом, но и значением базового типа, например переменной типа Number выше.useState
Возвращается массив, первое из которых — фактическое значение текущего состояния, а второе — функция для изменения этого состояния, что-то вродеsetState
. функция обновления сsetState
То же самое, что оба могут принимать два типа параметров, значение и функцию, иuseState
Разница в том, что функция обновления заменяет состояние, а не объединяет его.
Если в функциональном компоненте есть несколько состояний, вы можете передатьuseState
Объявите состояние типа объекта, также черезuseState
Укажите несколько раз.
// 声明对象类型的状态
const [count, setCount] = useState({
count1: 0,
count2: 0
});
// 多次声明
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
По сравнению с объявлением состояния типа объекта, очевидно, удобнее объявить состояние несколько раз, в основном потому, что функция обновления принимает метод замены, поэтому вам приходится добавлять неизменяемые атрибуты к параметрам, что очень хлопотно. Следует отметить, что React записывает каждое внутреннее состояние через последовательность вызовов Hook, поэтому Hook нельзя вызывать в условных операторах (таких как if) или операторах цикла, и следует отметить, что мы можем вызывать его только в функциональных компонентах Hooks, Хуки нельзя вызывать в компонентах и обычных функциях (кроме пользовательских хуков).
Когда мы хотим иметь дело со сложной многоуровневой логикой данных в функциональных компонентах, использование useState становится недоступным.К счастью, React предоставляет нам useReducer для обработки сложной логики состояния в функциональных компонентах. Если вы использовали Redux, то useReducer очень удобен, давайте перепишем предыдущий пример счетчика с useReducer:
import React, { useReducer } from 'react'
const reducer = function (state, action) {
switch (action.type) {
case "increment":
return { count : state.count + 1};
case "decrement":
return { count: state.count - 1};
default:
return { count: state.count }
}
}
function Example() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
const {count} = state;
return (
<div>
<span>{count}</span>
<button onClick={()=> dispatch({ type: "increment"})}>+</button>
<button onClick={() => dispatch({ type: "decrement"})}>-</button>
</div>
);
}
useReducer принимает два параметра: функцию редуктора и значение по умолчанию, и возвращает массив текущего состояния состояния и функции отправки, и его логика в основном такая же, как у Redux. Разница между useReducer и Redux заключается в значении по умолчанию.Значение Redux по умолчанию задается путем назначения параметров по умолчанию функции редуктора, например:
// Redux的默认值逻辑
const reducer = function (state = { count: 0 }, action) {
switch (action.type) {
case "increment":
return { count : state.count + 1};
case "decrement":
return { count: state.count - 1};
default:
return { count: state.count }
}
}
Причина, по которой useReducer не использует логику Redux, заключается в том, что React считает, что значение состояния по умолчанию может быть реквизитом из функциональных компонентов, например:
function Example({initialState = 0}) {
const [state, dispatch] = useReducer(reducer, { count: initialState });
// 省略...
}
Таким образом, значение состояния по умолчанию может быть определено путем передачи реквизита.Конечно, хотя React не рекомендует метод значения по умолчанию Redux, он также позволяет назначать значения по умолчанию аналогично Redux. Это затронет третий параметр useReducer: инициализация.
Как следует из названия, третий параметр initialization используется для инициализации состояния. Когда useReducer инициализирует состояние, он передает второй параметр initialState в функцию инициализации. Значение, возвращаемое функцией initialState, является начальным состоянием состояния, которое позволяет абстрагироваться за пределы редуктора.Функция предназначена для вычисления начального состояния состояния. Например:
const initialization = (initialState) => ({ count: initialState })
function Example({initialState = 0}) {
const [state, dispatch] = useReducer(reducer, initialState, initialization);
// 省略...
}
Итак, с помощью функции инициализации мы можем смоделировать начальное значение Redux:
import React, { useReducer } from 'react'
const reducer = function (state = {count: 0}, action) {
// 省略...
}
function Example({initialState = 0}) {
const [state, dispatch] = useReducer(reducer, undefined, reducer());
// 省略...
}
Side Effects: useEffect useLayoutEffect
解决了函数组件中内部状态的定义,接下来亟待解决的函数组件中生命周期函数的问题。在函数式思想的React中,生命周期函数是沟通函数式和命令式的桥梁,你可以在生命周期中执行相关的副作用(Side Effects),例如: 请求数据、操作DOM等。 React提供了useEffect来处理副作用。 Например:
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`
return () => {
console.log('clean up!')
}
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
В приведенном выше примере мы даемuseEffect
Функция передается, и заголовок страницы обновляется в соответствии со значением счетчика внутри функции. Мы обнаружим, что функция обратного вызова в useEffect будет вызываться каждый раз при обновлении компонента. Таким образом, мы можем думать об useEffect как о комбинации componentDidMount и componentDidUpdate. Когда компонент будет смонтирован (Mounted) и обновлен (Updated), будет вызвана функция обратного вызова. Обратите внимание, что в приведенном выше примере функция обратного вызова возвращает функцию, которая специально используется для очистки побочных эффектов.Мы знаем, что побочные эффекты, подобные событиям прослушивания, должны быть очищены вовремя, когда компонент выгружается, иначе это вызовет утечку памяти. Функция очистки вызывается перед повторным рендерингом каждого компонента, поэтому порядок выполнения следующий:
render -> effect callback -> re-render -> clean callback -> effect callback
поэтому мы можем использоватьuseEffect
Имитация поведения componentDidMount, componentDidUpdate, componentWillUnmount. Как мы упоминали ранее, именно из-за функции жизненного цикла мы должны разделить связанный код на разные функции жизненного цикла и вместо этого поместить нерелевантный код в ту же функцию жизненного цикла Причина, по которой это происходит, в основном проблема заключается в том, что мы пишем код не по бизнес-логике, а по времени выполнения. Чтобы решить эту проблему, мы можем решить вышеуказанную проблему, создав несколько хуков и поместив соответствующий логический код в один и тот же хук:
import React, { useState, useEffect } from 'react';
function Example() {
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return function cleanup() {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
useEffect(() => {
otherAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return function cleanup() {
otherAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
// 省略...
}
Мы используем несколько хуков, чтобы сосредоточить внимание на логических проблемах, чтобы избежать логической путаницы, вызванной сочетанием несвязанных кодов. Но тогда возникает проблема, если предположить, что одно из наших поведений направлено на то, чтобы различатьcomponentDidUpdate
илиcomponentDidMount
выполняется, когдаuseEffect
можно отличить. к счастьюuseEffect
Предоставляет нам второй параметр, если второй параметр передается в массиве, только при изменении значения в массиве при повторном рендеринге,useEffect
Функция обратного вызова будет выполнена. Поэтому, если мы передаем ему пустой массив, мы можем смоделировать жизненный циклcomponentDidMount
. Но если вы хотите моделировать толькоcomponentDidUpdate
, до сих пор не было найдено ни одного хорошего метода.
useEffect
В отличие от жизненного цикла компонента класса,componentDidUpdate
а такжеcomponentDidMount
Оба выполняются синхронно после обновления DOM, ноuseEffect
Он не будет выполняться синхронно после обновления DOM и не будет блокировать интерфейс обновления. Если вам нужно имитировать эффекты синхронизации жизненного цикла, вам нужно использоватьuseLayoutEffect
, его использование иuseEffect
Опять же, область находится только во время выполнения.
Контекст: использоватьконтекст
С крючком:useContext
Мы также можем использовать функциональную сборкуcontext
. По сравнению с использованием реквизитов рендеринга в компонентах класса,useContext
вполне удобен в использовании.
import { createContext } from 'react'
const ThemeContext = createContext({ color: 'color', background: 'black'});
function Example() {
const theme = useContext(Conext);
return (
<p style={{color: theme.color}}>Hello World!</p>
);
}
class App extends Component {
state = {
color: "red",
background: "black"
};
render() {
return (
<Context.Provider value={{ color: this.state.color, background: this.state.background}}>
<Example/>
<button onClick={() => this.setState({color: 'blue'})}>color</button>
<button onClick={() => this.setState({background: 'blue'})}>backgroud</button>
</Context.Provider>
);
}
}
useContext
принять функциюReact.createContext
Возвращенный объект контекста используется в качестве параметра для возврата значения в текущем контексте. в любое времяProvider
При изменении значения функция будет повторно отображать компонент, следует отметить, что даже при изменении значения неиспользуемого контекста функция будет повторно отображать компонент, как в примере выше,Example
компонент, даже если он не используетсяbackground
,ноbackground
когда происходят изменения,Example
также будет повторно отображаться. Поэтому при необходимости, еслиExample
Компоненты также имеют подкомпоненты, вам может потребоваться добавитьshouldComponentUpdate
Предотвратите ненужный рендеринг из-за потери производительности.
Ref: useRef useImperativeHandle
useRef
Распространенные примеры доступа к дочерним элементам:
function Example() {
const inputEl = useRef();
const onButtonClick = () => {
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
Мы сказали вышеuseRef
обычно используется вref
свойства, на самом делеuseRef
делает больше, чем это
const refContainer = useRef(initialValue)
useRef
может принимать значение по умолчанию и возвращатьcurrent
Изменяемый объект свойств, который будет сохраняться в течение всего времени жизни компонента. Поэтому его можно использовать как свойство компонента класса.
useImperativeHandle
Используется для пользовательского доступа к родительским компонентамref
Атрибуты. нужно сотрудничатьforwardRef
использовать вместе.
function Example(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} />;
}
export default forwardRef(Example);
class App extends Component {
constructor(props){
super(props);
this.inputRef = createRef()
}
render() {
return (
<>
<Example ref={this.inputRef}/>
<button onClick={() => {this.inputRef.current.focus()}}>Click</button>
</>
);
}
}
New Feature: useCallback useMemo
Студенты, знакомые с React, видели похожие сценарии:
class Example extends React.PureComponent{
render(){
// ......
}
}
class App extends Component{
render(){
return <Example onChange={() => this.setState()}/>
}
}
На самом деле в этом сценарии, хотяExample
наследоватьPureComponent
, но на самом деле не оптимизирует производительность, потому что каждый разApp
компонент, переданный вonChange
свойство является новым экземпляром функции, поэтому каждый разExample
будет перерисован. Чтобы решить эту ситуацию, мы обычно используем следующие методы:
class App extends Component{
constructor(props){
super(props);
this.onChange = this.onChange.bind(this);
}
render(){
return <Example onChange={this.onChange}/>
}
onChange(){
// ...
}
}
Описанный выше метод решает сразу две задачи: во-первых, он обеспечиваетExample
компонентonChange
Свойства — это тот же экземпляр функции, и для решения функции обратного вызоваthis
Связывание. Итак, как решить проблему функциональные компоненты, присутствующие в нем? РеагироватьuseCallback
Функция кэширования обработчиков событий.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
useCallback принимает функцию и массив на входе и возвращает кешированную версию функции обратного вызова. Только когда значение в массиве изменяется во время повторного рендеринга, будет возвращен новый экземпляр функции, что также решает упомянутый выше оптимизатор. производительности компонентов, и не будет утомительных шагов выше.
а такжеuseCallback
По аналогии,useMemo
То, что возвращается, является кэшированным значением.
const memoizedValue = useMemo(
() => complexComputed(),
[a, b],
);
То есть только при изменении значения в массиве при повторном рендеринге функция обратного вызова будет пересчитывать закешированные данные, что позволяет нам избежать сложных вычислений данных при каждом повторном рендеринге. Итак, мы можем думать, что:
useCallback(fn, input)
ЭквивалентноuseMemo(() => fn, input)
Если не указаноuseMemo
Передайте второй параметр, затемuseMemo
Он будет пересчитан только при получении нового экземпляра функции.Следует отметить, что официальная документация React напоминает нам,useMemo
Его можно использовать только как средство оптимизации производительности, а не как семантическую гарантию, то есть в некоторых случаях React будет выполняться повторно, даже если данные в массиве не изменились.
Пользовательский крючок
Как мы уже говорили, хук может вызываться только в верхней части функционального компонента и не может использоваться в циклах, условиях или обычных функциях. Как мы упоминали ранее, для компонентов класса очень проблематично совместно использовать логику состояния, и очень обременительно использовать реквизиты рендеринга и HOC. Напротив, React позволяет нам создавать собственные хуки для инкапсуляции логики общего состояния. Так называемый пользовательский хук относится к имени функции, начинающемуся сuse
Запускайте и вызывайте другие функции Hook. Мы используем пользовательский хук, чтобы переписать исходный пример пользовательского состояния с подпиской:
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(isOnline) {
setIsOnline(isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
function FriendStatus() {
const isOnline = useFriendStatus();
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
Мы переписали предыдущий пример онлайн-статуса подписанного пользователя с пользовательским хуком.По сравнению со сложной логикой рендеринга и HOC, пользовательский хук более лаконичен.Случай ада компонентной оболочки, который был посещен. Он элегантно решает проблему повторного использования логики состояния для компонентов класса.
Суммировать
С помощью хуков функциональные компоненты могут в основном реализовать функции большинства компонентов класса.Кроме того, хуки имеют определенные преимущества в совместном использовании логики состояния и улучшении ремонтопригодности компонентов. Можно предвидеть, что хуки, вероятно, станут направлением обозримого будущего React. React официально принял Стратегию постепенного внедрения хуков и заявил, что в настоящее время нет планов по удалению классов из React.Видно, что хуки будут работать параллельно с нашим существующим кодом в течение длительного времени.React не рекомендует нас Все предыдущие Компоненты класса переписываются с помощью хуков, но рекомендуется использовать хуки в новых или некритических компонентах. Если в выражении есть неточности, прошу смиренно принять критику и совет. Желаем всем вместе прогрессировать!