предисловие
Я читал и читал много статей о крючках на Наггетс, но чувствую, что они не очень подробные. А также много гидрологии. В последнее время я планирую снова научиться реагировать и систематически снова изучать хуки.
Крючки что ли?
- react-hooks — это новый API-интерфейс для реакции после react16.8, который позволяет вам использовать состояние и другие функции React без написания классов.
- Хуки — это функции, которые позволяют вам «подключаться» к функциям состояния и жизненного цикла React в функциональных компонентах.
Зачем использовать хуки?
Недостатки классовых компонентов: (отМотивация сайта)
-
Повторное использование логики состояния между компонентами сложно
-
Сложные компоненты становятся трудными для понимания
-
непонятный класс
Вы должны понимать JavaScriptthis
работает, что значительно отличается от других языков. И не забудьте привязать обработчики событий. нестабильныйПредложение по грамматике, код очень избыточен.
Появление крючков решает вышеуказанные проблемы. Кроме того, есть и другие преимущества:
- Увеличьте возможность повторного использования и логику кода, компенсируйте дефект, заключающийся в том, что компоненты без состояния не имеют жизненного цикла и состояния управления данными.
- Идея реактивных хуков ближе к функциональному программированию. Используйте объявление функции вместо объявления класса.Хотя class также является синтаксическим сахаром конструктора es6, react-hooks записывает функции как компоненты, что, несомненно, повышает эффективность разработки кода (нет необходимости писать цикл объявления и жизненный цикл, как компоненты объявления класса. функция рендеринга и т. д.)
Никаких критических изменений для хуков
- Совершенно необязательно.Вы можете попробовать хуки в некоторых компонентах, не переписывая существующий код. Но если вы не хотите, вам не нужно изучать или использовать хуки прямо сейчас.
- 100% обратная совместимость.Хуки не содержат критических изменений.
- Доступен сейчас.Хук был выпущен в версии 16.8.0.
Правила использования хуков
1. Используйте хуки только на верхнем уровне, не вызывайте хуки в циклах, условных выражениях или вложенных функциях.
Обязательно всегда вызывайте их на верхнем уровне ваших функций React. Следуя этому правилу, вы можете гарантировать, что хуки вызываются в одном и том же порядке при каждом рендеринге. Это позволяет ReactuseState
и useEffect
Поддерживайте правильное состояние ловушки между вызовами.
2. Вызывайте хуки только в функциях React
Вместо того, чтобы вызывать хуки в обычных функциях JavaScript, вы можете:
- ✅ Call Hook в функциональном компоненте React
- ✅ Вызов других хуков в пользовательских хуках
Что касается того, почему существуют эти правила, если вы заинтересованы, пожалуйста, обратитесь кПравила хука
useState
const [state, setState] = useState(initialState)
- useState имеет параметр (initialState может быть функцией, возвращающей значение, но обычно не используется), этот параметр может иметь любой тип данных и обычно используется как значение по умолчанию.
- Возвращаемое значение useState — это массив, первый параметр массива — это состояние, которое нам нужно использовать, а второй параметр — это функция для изменения состояния (функция аналогична this.setState)
Взгляните на случай таймера
import React,{useState} from "react";
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
-
Первая строка:Представлено в React
useState
Крюк. Это позволяет нам хранить внутреннее состояние в функциональных компонентах. -
Третий ряд: существует
Example
Внутри компонента мы вызываемuseState
Хук объявляет новую переменную состояния. Он возвращает пару значений нашей именованной переменной. Мы называем переменную какcount
, потому что он хранит клики. Мы проходим0
в видеuseState
единственный параметр для его инициализации как0
. Второе возвращаемое значение само по себе является функцией. это позволяет нам обновлятьcount
значение , поэтому мы называем этоsetCount
. -
Седьмая строка:Когда пользователь нажимает кнопку, мы передаем новое значение в
setCount
. React будет повторно отображатьExample
компоненты, и поставить последниеcount
перейти к нему.
Использование нескольких переменных состояния
// 声明多个 state 变量
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: '学习 Hook' }]);
тыне нужноИспользуйте несколько переменных состояния. Переменные состояния отлично подходят для хранения объектов и массивов, поэтому вы можете группировать связанные данные вместе.
Обновить состояние
import React,{useState} from "react";
function Example() {
const [count, setCount] = useState(0);
const [person, setPerson] = useState({name:'jimmy',age:22});
return (
<div>
<p>name {person.name} </p>
// 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将回调函数当做参数传递给 setState。
// 该回调函数将接收先前的 state,并返回一个更新后的值。
<button onClick={() => setCount(count=>count+1)}>Click me</button>
<button onClick={() => setPerson({name:'chimmy'})}>Click me</button>
</div>
);
}
export default Example;
Когда setPerson обновляет человека, в отличие от классаthis.setState
, всегда обновляя переменную состояниязаменятьэто вместо слияния его. Человек в приведенном выше примере {name:'chimmy'} вместо {name:'chimmy',age:22}
useEffect
Effect HookПозволяет выполнять побочные эффекты (извлечение данных, настройка подписок и ручное изменение DOM в компонентах React — все это побочные эффекты) в функциональных компонентах.
useEffect(fn, array)
useEffect выполняется один раз после первоначального рендеринга, со вторым параметром для имитации некоторых жизненных циклов класса.
Если вы знакомы с функциями жизненного цикла класса React, вы можете поместитьuseEffect
Крюк какcomponentDidMount``componentDidUpdate
и componentWillUnmount
Сочетание этих трех функций.
useEffect реализует компонентDidMount
Если второй параметр является пустым массивом, useEffect эквивалентен componentDidMount в компоненте класса.
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("我只会在组件初次挂载完成后执行");
}, []);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
После рендеринга страницы useEffect выполняется один раз. Вывести "Я выполню только после того, как компонент смонтируется в первый раз" При изменении состояния по нажатию кнопки и повторном рендеринге страницы useEffect выполняться не будет.
useEffect реализует компонентDidUpdate
Если вы не передадите второй параметр, useEffect будет выполняться при первом рендеринге и каждом обновлении.
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("我会在初次组件挂载完成后以及重新渲染时执行");
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
export default Example;
При первом рендеринге useEffect будет выполнен один раз и выведет «Я выполню его после завершения начального монтирования компонента и повторного рендеринга». При нажатии кнопки состояние изменяется, страница перерисовывается, выполняется useEffect и «Я буду выполняться после завершения начального монтирования компонента и его повторного рендеринга».
useEffect реализует компонентWillUnmount
Эффект возвращает функцию, которую React вызовет при выполнении операции очистки.
useEffect(() => {
console.log("订阅一些事件");
return () => {
console.log("执行清除操作")
}
},[]);
Примечание. «Выполненная очистка» печатается не только при уничтожении компонента, но и при каждом повторном рендеринге. Что касается причины, я думаю, что официальный сайт объясняет это очень четко, пожалуйста, обратитесь к объяснению:Почему вам нужно запускать Эффект каждый раз, когда вы обновляете
Контролировать выполнение useEffect
import React, { useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const [number, setNumber] = useState(1);
useEffect(() => {
console.log("我只会在cout变化时执行");
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click cout</button>
<button onClick={() => setNumber(number + 1)}>Click number</button>
</div>
);
}
export default Example;
В приведенном выше примере при нажатии кнопки cout будет напечатано «Я буду выполнять только при изменении cout». Поскольку зависимостью в массиве второго параметра useEffect является cout, useEffect будет выполняться только при изменении cout. Если в массиве несколько элементов, React выполнит эффект, даже если изменится только один элемент.
Разделение проблем с использованием нескольких эффектов
Используйте один из крючковЦельИменно для решения проблемы функции жизненного цикла в классе часто содержат несвязанную логику, но связанная логика разделена на несколько разных методов.
import React, { useState, useEffect } from "react";
function Example() {
useEffect(() => {
// 逻辑一
});
useEffect(() => {
// 逻辑二
});
useEffect(() => {
// 逻辑三
});
return (
<div>
useEffect的使用
</div>
);
}
export default Example;
Хуки позволяют нам разделить код по назначению,вместо функции жизненного цикла. React будет вызывать компоненты в том порядке, в котором объявлены эффекты.Каждыйэффект.
Использование асинхронных функций в useEffect
useEffect не может напрямую использовать async await Синтаксический сахар
/* 错误用法 ,effect不支持直接 async await*/
useEffect(async ()=>{
/* 请求数据 */
const res = await getData()
},[])
useEffect
Параметр обратного вызова возвращает функцию, которая устраняет побочные эффекты.clean-up
функция. так не могу вернутьсяPromise
, и нельзя использоватьasync/await
Итак, как мы должны сделатьuseEffect
служба поддержкиasync/await
Шерстяная ткань?
Способ 1 (рекомендуется)
const App = () => {
useEffect(() => {
(async function getDatas() {
await getData();
})();
}, []);
return <div></div>;
};
Способ второй
useEffect(() => {
const getDatas = async () => {
const data = await getData();
setData(data);
};
getDatas();
}, []);
что делает useEffect
Используя этот хук, вы можете сообщить компонентам React, что渲染后
выполнить какое-либо действие. React сохраняет переданную вами функцию (назовем ее «эффектом») и вызывает ее после выполнения обновления DOM.
Зачем вызывать внутри компонентаuseEffect
?
будет useEffect
Помещение его внутрь компонента позволяет нам напрямую обращаться к нему в эффекте.count
переменные состояния (или другие реквизиты). Нам не нужен специальный API для его чтения — он уже хранится в области видимости функции. Хуки используют механизм закрытия JavaScript вместо того, чтобы вводить определенные API React, когда JavaScript уже предоставляет решение.
useContext
концепция
const value = useContext(MyContext);
получает объект контекста (React.createContext
возвращаемое значение ) и возвращает текущее значение этого контекста. Когда верхний слой компонента является ближайшим<MyContext.Provider>
При обновлении этот хук вызовет повторный рендеринг и использует последний переданныйMyContext
контекст провайдераvalue
ценность. даже если предки используютReact.memo
или shouldComponentUpdate
, также используется в самом компонентеuseContext
при повторном рендеринге.
Не забудьте useContext
Параметр должен бытьсам объект контекста:
-
правильный:
useContext(MyContext)
-
Ошибка:
useContext(MyContext.Consumer)
-
Ошибка:
useContext(MyContext.Provider)
Пример
index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
// 创建两个context
export const UserContext = React.createContext();
export const TokenContext = React.createContext();
ReactDOM.render(
<UserContext.Provider value={{ id: 1, name: "chimmy", age: "20" }}>
<TokenContext.Provider value="我是token">
<App />
</TokenContext.Provider>
</UserContext.Provider>,
document.getElementById("root")
);
app.js
import React, { useContext } from "react";
import { UserContext, TokenContext } from "./index";
function Example() {
let user = useContext(UserContext);
let token = useContext(TokenContext);
console.log("UserContext", user);
console.log("TokenContext", token);
return (
<div>
name:{user?.name},age:{user?.age}
</div>
);
}
export default Example;
Напечатанное значение выглядит следующим образом
намекать
Если вы уже знакомы с контекстным API до того, как коснулись хука, это должно быть понятно.useContext(MyContext)
Эквивалент в компоненте классаstatic contextType = MyContext
или <MyContext.Consumer>
.
useContext(MyContext)
просто чтобы вы могличитатьЗначение контекста и изменения контекста подписки. Вам все еще нужно использовать в верхнем дереве компонентов<MyContext.Provider>
для компонентов нижнего уровняпоставкаконтекст.
useReducer
концепция
const [state, dispatch] = useReducer(reducer, initialArg, init);
useState
альтернатива. Он получает(state, action) => newState
Редуктор и возвращает текущее состояние и его соответствиеdispatch
метод. (Если вы знакомы с Redux, вы уже знаете, как он работает.)
В некоторых сценарияхuseReducer
будет лучше, чемuseState
Более применимо, например, логика состояния более сложна и содержит несколько подзначений, или следующее состояние зависит от предыдущего состояния и т. д. и, используяuseReducer
Он также может выполнять оптимизацию производительности для компонентов, запускающих глубокие обновления, поскольку вы можете передавать их дочерним компонентам.dispatch
вместо функции обратного вызова
будь осторожен
React позаботится о том, чтобы
dispatch
Идентификатор функции стабилен и не меняется при повторном рендеринге компонента. Вот почему безопасноuseEffect
илиuseCallback
исключен из списка зависимостейdispatch
.
Пример
import React, { useReducer } from "react";
export default function Home() {
function reducer(state, action) {
switch (action.type) {
case "increment":
return { ...state, counter: state.counter + 1 };
case "decrement":
return { ...state, counter: state.counter - 1 };
default:
return state;
}
}
const [state, dispatch] = useReducer(reducer, { counter: 0 });
return (
<div>
<h2>Home当前计数: {state.counter}</h2>
<button onClick={(e) => dispatch({ type: "increment" })}>+1</button>
<button onClick={(e) => dispatch({ type: "decrement" })}>-1</button>
</div>
);
}
useCallback
концепция
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
Возвращает [запоминаемую] функцию обратного вызова.
Передайте встроенную функцию обратного вызова и массив зависимостей в качестве параметровuseCallback
, который возвращает запомненную версию функции обратного вызова, которая обновляется только при изменении зависимости. Когда вы передаете обратные вызовы для оптимизации и используете ссылочное равенство, чтобы избежать ненужного рендеринга (например,shouldComponentUpdate
), это будет очень полезно.
Пример
import React, { useState } from "react";
// 子组件
function Childs(props) {
console.log("子组件渲染了");
return (
<>
<button onClick={props.onClick}>改标题</button>
<h1>{props.name}</h1>
</>
);
}
const Child = React.memo(Childs);
function App() {
const [title, setTitle] = useState("这是一个 title");
const [subtitle, setSubtitle] = useState("我是一个副标题");
const callback = () => {
setTitle("标题改变了");
};
return (
<div className="App">
<h1>{title}</h1>
<h2>{subtitle}</h2>
<button onClick={() => setSubtitle("副标题改变了")}>改副标题</button>
<Child onClick={callback} name="桃桃" />
</div>
);
}
Результат выполнения следующий
когда я нажимаюизменить субтитрыПосле этой кнопки субтитр изменится на «Субтитры изменены», и консоль снова распечатает子组件渲染了
, что доказывает, что дочерний компонент перерисовывается, но в дочернем компоненте нет изменений, поэтому перерисовка дочернего компонента на этот раз избыточна, так как же избежать этого избыточного рендеринга?
найти причину
Прежде чем мы решим проблему,Прежде всего, в чем причина этой проблемы?
Разберем, перерисовывается компонент, вообще бывает три случая:
- Либо изменяется собственное состояние компонента
- Либо родительский компонент повторно рендерится, вызывая повторный рендеринг дочернего компонента, но реквизиты родительского компонента не изменились
- Либо родительский компонент повторно рендерится, вызывая повторный рендеринг дочернего компонента, но реквизиты, переданные родительским компонентом, изменяются.
Затем используйте метод исключения, чтобы узнать, что вызвало это:
Первый явно исключается, когда щелчокизменить субтитрыКогда это не меняет состояние дочернего компонента;
Во втором случае используемReact.memo
решить эту проблему, поэтому эта ситуация также исключена.
Затем есть третий случай.Когда родительский компонент перерисовывается, реквизиты, переданные дочернему компоненту, изменились.Посмотрите на два свойства, переданные дочернему компоненту, одно из нихname
,одинonClick
,name
передана константа, она не изменится, какие измененияonClick
Теперь, почему функция обратного вызова, переданная в onClick, изменяется? На самом деле, каждый раз, когда функциональный компонент перерисовывается, функциональный компонент будет повторно выполняться с самого начала, поэтому функция обратного вызова, созданная эти два раза, должна была измениться, что вызвало повторный рендеринг подкомпонента.
Решаем проблему с помощью useCallback
const callback = () => {
doSomething(a, b);
}
const memoizedCallback = useCallback(callback, [a, b])
Передать функции и зависимости в качестве параметровuseCallback
, который вернет мемоизированную версию функции обратного вызова, этот memoizedCallback будет обновляться только при изменении зависимостей.
Затем просто измените функцию обратного вызова, переданную дочернему компоненту таким образом, и все будет в порядке.
const callback = () => { setTitle("标题改变了"); };
// 通过 useCallback 进行记忆 callback,并将记忆的 callback 传递给 Child
<Child onClick={useCallback(callback, [])} name="桃桃" />
Таким образом, мы видим, что рендеринг подкомпонента будет напечатан только при первом рендеринге, а рендеринг подкомпонента не будет напечатан при щелчке, чтобы изменить субтитр и изменить заголовок.
useMemo
концепция
const cacheSomething = useMemo(create,deps)
-
create
: первый параметр — это функция, а возвращаемое значение функции используется как кэшированное значение. -
deps
: Второй параметр — это массив, в котором хранятся зависимости текущего useMemo. Когда компонент функции будет выполняться в следующий раз, он сравнит состояние в зависимостях deps, чтобы увидеть, есть ли какие-либо изменения. -execute create для получения нового кэшированного значения. -
cacheSomething
: возвращаемое значение, возвращаемое значение выполнения create. Если есть изменение зависимости в deps, возвращаемое значение является значением, сгенерированным повторным выполнением create, в противном случае берется последнее кэшированное значение.
Принцип использования Памятка
useMemo запишет возвращаемое значение последнего выполнения create и привяжет его к объекту волокна, соответствующему функциональному компоненту.Пока компонент не уничтожен, кэшированное значение всегда будет существовать, но если есть изменение в deps, create будет выполнен повторно.Возвращенное значение записывается в объект волокна как новое значение.
Пример
function Child(){
console.log("子组件渲染了")
return <div>Child</div>
}
const Child = memo(Child)
function APP(){
const [count, setCount] = useState(0);
const userInfo = {
age: count,
name: 'jimmy'
}
return <Child userInfo={userInfo}>
}
Когда функциональный компонент перерисовывается, userInfo каждый раз будет новым объектом, независимо отcount
Независимо от того, есть ли изменение или нет, оно вызовет повторную визуализацию дочернего компонента.
И следующее будет вcount
Новый объект возвращается только после изменения.
function Child(){
console.log("子组件渲染了")
return <div>Child</div>
}
function APP(){
const [count, setCount] = useState(0);
const userInfo = useMemo(() => {
return {
name: "jimmy",
age: count
};
}, [count]);
return <Child userInfo={userInfo}>
}
На самом деле, роль useMemo больше, чем это Согласно введению в официальном документе: поместить в useMemo некоторую дорогостоящую логику вычислений и обновлять ее только при изменении зависимого значения.
import React, {useState, useMemo} from 'react';
// 计算和的函数,开销较大
function calcNumber(count) {
console.log("calcNumber重新计算");
let total = 0;
for (let i = 1; i <= count; i++) {
total += i;
}
return total;
}
export default function MemoHookDemo01() {
const [count, setCount] = useState(100000);
const [show, setShow] = useState(true);
const total = useMemo(() => {
return calcNumber(count);
}, [count]);
return (
<div>
<h2>计算数字的和: {total}</h2>
<button onClick={e => setCount(count + 1)}>+1</button>
<button onClick={e => setShow(!show)}>show切换</button>
</div>
)
}
Когда мы переходим к нажатию кнопки переключения показа, функция, которая вычисляет сумму calcNumber, не отображается, только когда количество изменяется, вычисление происходит.
Сводка useCallback и useMemo
Простое понимание useCallback и useMemo состоит в том, что один кэш — это функция, а другой — результат, возвращаемый функцией. useCallback предназначен для оптимизации дочерних компонентов и предотвращения повторного рендеринга дочерних компонентов. useMemo может оптимизировать текущий компонент, а также подкомпоненты.Оптимизация текущего компонента в основном кэширует некоторую сложную вычислительную логику через memoize. Конечно, нет необходимости использовать useMemo, если он просто выполняет простые вычисления.
Мы можем определить возвращаемое значение useMemo как возвращающую функцию, чтобы мы могли гибко реализовать useCallback.useCallback(fn, deps)
эквивалентноuseMemo(() => fn, deps)
.
useRef
const refContainer = useRef(initialValue);
useRef
Возвращает изменяемый объект ref, чей.current
Свойство инициализируется переданным параметром (initialValue
). Возвращенный объект ref остается неизменным в течение всего времени жизни компонента.
useRef получить дом
useRef, у него есть параметр, который можно использовать в качестве начального значения кэшированных данных, возвращаемое значение может быть помечено ссылкой на элемент dom, и можно получить отмеченный узел элемента.
import React, { useRef } from "react";
function Example() {
const divRef = useRef();
function changeDOM() {
// 获取整个div
console.log("整个div", divRef.current);
// 获取div的class
console.log("div的class", divRef.current.className);
// 获取div自定义属性
console.log("div自定义属性", divRef.current.getAttribute("data-clj"));
}
return (
<div>
<div className="div-class" data-clj="我是div的自定义属性" ref={divRef}>
我是div
</div>
<button onClick={(e) => changeDOM()}>获取DOM</button>
</div>
);
}
export default Example;
Данные кеша useRef
Еще одна важная роль useRef — кэширование данных.Мы знаем, что usestate и useReducer могут сохранять текущий источник данных, но если они обновят выполнение функции источника данных, то это обязательно приведет весь компонент из нового выполнения в рендеринг. объявите переменную внутри, следующее обновление также сбросит ее.Если мы хотим сохранить данные тихо, не запуская обновление функции, то useRef — отличный выбор.
Вот пример изменения последнего значения состояния каждый раз
import React, { useRef, useState, useEffect } from "react";
function Example() {
const [count, setCount] = useState(0);
const numRef = useRef(count);
useEffect(() => {
numRef.current = count;
}, [count]);
return (
<div>
<h2>count上一次的值: {numRef.current}</h2>
<h2>count这一次的值: {count}</h2>
<button onClick={(e) => setCount(count + 10)}>+10</button>
</div>
);
}
export default Example;
Когда содержимое объекта ref изменяется,useRef
иНе будусообщаю тебе. изменять.current
Свойство не вызывает повторную визуализацию компонента. Таким образом, в приведенном выше примере, хотя значение numRef.current изменилось, последнее значение по-прежнему отображается на странице, а последнее обновленное значение будет отображаться только при повторном обновлении.
напиши в конце
Если в статье есть ошибки, укажите на них.