Эта статья взята из«Одна статья полностью понимает принцип и реализацию реагирующих хуков», если вы чувствуете себя хорошо, добро пожаловать вРепозиторий на гитхабезвезда.
Резюме
При крючке характерных компонентов времени приготовления всегда чувствую его простоту и удобство. Конечно, «нет бесплатного обеда», оно за счет читаемости, и есть риск утечки памяти (последний упомянутый). Но это не мешает исследовать свою магию.
Прежде чем начать, я надеюсь, что вы прочитали документацию по хуку или использовали его в проекте. Но если вас интересует только функциональное программирование, вы можете что-то выиграть.
Для того, чтобы писать было более плавно, планирую сначала закинуть несколько вопросов, которые постепенно будут решаться в процессе реализации исходников:
- 🤔️ Принцип реализации useState
- 🤔️ Почему нельзя использовать хук внутри цикла и суждение
- 🤔️ Принцип реализации useEffect
- 🤔️ Сценарии применения UseEffect
- 🤔️
Class
vsHooks
⚠️ Все коды предоставленыTypeScript
Для достижения все демки в тексте находятся вgist.GitHub.com/dongyuanxin…
Принцип реализации useState
При вызове useState он вернет что-то вроде(变量, 函数)
предок . А начальное значение состояния — это параметр, передаваемый при внешнем вызове useState.
После уточнения параметров и возвращаемых значений давайте посмотрим, что еще делает useState. Как показано в приведенном ниже коде, при нажатии кнопки выполняетсяsetNum
, номер состояния обновляется,и обновление представления пользовательского интерфейса. Очевидно, функция, используемая для изменения состояния, возвращаемого useState, вызывается автоматически.render
метод для запуска обновления представления.
function App() {
const [num, setNum] = useState < number > 0;
return (
<div>
<div>num: {num}</div>
<button onClick={() => setNum(num + 1)}>加 1</button>
</div>
);
}
С помощью приведенного выше исследования с помощью замыканий инкапсулируйтеsetState
следующим образом:
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
let state: any;
function useState<T>(initialState: T): [T, (newState: T) => void] {
state = state || initialState;
function setState(newState: T) {
state = newState;
render();
}
return [state, setState];
}
render(); // 首次渲染
Это простой в использованииuseState
Прототип. Это также решило проблему «🤔️ принципа реализации USSTATE», начатую статьей. Но если вы объявите несколько состояний внутри функции, в текущем коде будет действовать только первое состояние (см.state = state || initialState;
)).
Почему нельзя использовать Крюк внутри цикла и суждения
Пока не думайте о вопросе, упомянутом в заголовке. Идея восходит к тому, как заставить useState поддерживать несколько состояний.«React hooks: не магия, а просто массивы»Как упоминалось выше, React Hook выглядит очень волшебной реализацией, и по сути реализован через Array.
В приведенной выше простой реализации useState начальное состояние сохраняется в глобальной переменной. По аналогии несколько состояний должны храниться в выделенном глобальном контейнере. Этот контейнер представляет собой простенький объект Array. Конкретный процесс выглядит следующим образом:
- При первом рендеринге, в соответствии с порядком useState, состояния объявляются одно за другим и помещаются в глобальный массив. Каждый раз, когда объявляется состояние, курсор увеличивается на 1.
- Обновление состояния, запускающее повторный рендеринг.курсор сбрасывается на 0. В соответствии с порядком объявления useState извлеките последнее значение состояния по очереди, и представление будет обновлено.
Взгляните на рисунок ниже: каждый раз, когда используется useState, в контейнер STATE добавляется новое состояние.
Реализованный код выглядит следующим образом:
import React from "react";
import ReactDOM from "react-dom";
const states: any[] = [];
let cursor: number = 0;
function useState<T>(initialState: T): [T, (newState: T) => void] {
const currenCursor = cursor;
states[currenCursor] = states[currenCursor] || initialState; // 检查是否渲染过
function setState(newState: T) {
states[currenCursor] = newState;
render();
}
++cursor; // update: cursor
return [states[currenCursor], setState];
}
function App() {
const [num, setNum] = useState < number > 0;
const [num2, setNum2] = useState < number > 1;
return (
<div>
<div>num: {num}</div>
<div>
<button onClick={() => setNum(num + 1)}>加 1</button>
<button onClick={() => setNum(num - 1)}>减 1</button>
</div>
<hr />
<div>num2: {num2}</div>
<div>
<button onClick={() => setNum2(num2 * 2)}>扩大一倍</button>
<button onClick={() => setNum2(num2 / 2)}>缩小一倍</button>
</div>
</div>
);
}
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
cursor = 0; // 重置cursor
}
render(); // 首次渲染
На этом этапе, если вы хотите использовать хуки, где циклы, суждения и т. д. не находятся в верхней части функционального компонента, выполните следующие действия:
let tag = true;
function App() {
const [num, setNum] = useState < number > 0;
// 只有初次渲染,才执行
if (tag) {
const [unusedNum] = useState < number > 1;
tag = false;
}
const [num2, setNum2] = useState < number > 2;
return (
<div>
<div>num: {num}</div>
<div>
<button onClick={() => setNum(num + 1)}>加 1</button>
<button onClick={() => setNum(num - 1)}>减 1</button>
</div>
<hr />
<div>num2: {num2}</div>
<div>
<button onClick={() => setNum2(num2 * 2)}>扩大一倍</button>
<button onClick={() => setNum2(num2 / 2)}>缩小一倍</button>
</div>
</div>
);
}
Потому что в логике условного суждения сбросtag=false
, поэтому последующий рендеринг не войдет в оператор условного суждения. Вроде нет проблем? Однако, поскольку useState реализован на основе Array+Cursor, при первом рендеринге соответствующая связь между состоянием и курсором выглядит следующим образом:
имя переменной | cursor |
---|---|
num | 0 |
unusedNum | 1 |
num2 | 2 |
Когда событие щелчка инициирует повторную визуализацию, оно не войдет в useState в условном решении. Следовательно, когда курсор = 2, соответствующая переменная равна num2. Фактически, курсор, соответствующий num2, должен быть равен 3. приведет кsetNum2
не работает.
На данный момент вопрос «🤔️ почему нельзя использовать хук в петлях и суждениях», предложенный в начале статьи, решен. При использовании Hook, пожалуйста, используйте его в верхней части функционального компонента!
Принцип реализации useEffect
При изучении принципа useEffect меня беспокоил вопрос: какова функция и назначение useEffect? Конечно, любой может говорить о побочных эффектах функций. Например:
function App() {
const [num, setNum] = useState(0);
useEffect(() => {
// 模拟异步请求后端数据
setTimeout(() => {
setNum(num + 1);
}, 1000);
}, []);
return <div>{!num ? "请求后端数据..." : `后端数据是 ${num}`}</div>;
}
Этот код, хоть и организован таким образом, более читабелен, ведь запрос можно понимать как побочный эффект функции.но это не обязательно. совершенно бесполезныйuseEffect
, Используйте напрямуюsetTimeout
, а его функция обратного вызова обновляет состояние функционального компонента.
чтениеA Complete Guide to useEffectа такжеСоздайте свои собственные крючкиПосле этого я понял необходимость и значимость существования useEffect.
Во втором параметре useEffect мы можем указать массив, если элементы в массиве не изменятся в следующий раз, то этот побочный эффект не сработает (аналогично жизненному циклу nextprops и prevProps класса Class). Преимущества очевидны,По сравнению с записью непосредственно на верхнем уровне функциональных компонентов, useEffect позволяет избежать избыточного рендеринга по мере необходимости..
Вот реализация useEffect на TypeScript, которая не включает уничтожающие побочные эффекты:
// 还是利用 Array + Cursor的思路
const allDeps: any[][] = [];
let effectCursor: number = 0;
function useEffect(callback: () => void, deps: any[]) {
if (!allDeps[effectCursor]) {
// 初次渲染:赋值 + 调用回调函数
allDeps[effectCursor] = deps;
++effectCursor;
callback();
return;
}
const currenEffectCursor = effectCursor;
const rawDeps = allDeps[currenEffectCursor];
// 检测依赖项是否发生变化,发生变化需要重新render
const isChanged = rawDeps.some(
(dep: any, index: number) => dep !== deps[index]
);
if (isChanged) {
callback();
allDeps[effectCursor] = deps; // 感谢 juejin@carlzzz 的指正
}
++effectCursor;
}
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
effectCursor = 0; // 注意将 effectCursor 重置为0
}
Для реализации использования использования будет легче понять с использованием следующих случаев. Конечно, вы также можете инициировать асинхронный запрос в этом использовании, а после получения данных вызовите функцию обновления состояния, а стек не будет взорваться.
function App() {
const [num, setNum] = useState < number > 0;
const [num2] = useState < number > 1;
// 多次触发
// 每次点击按钮,都会触发 setNum 函数
// 副作用检测到 num 变化,会自动调用回调函数
useEffect(() => {
console.log("num update: ", num);
}, [num]);
// 仅第一次触发
// 只会在compoentDidMount时,触发一次
// 副作用函数不会多次执行
useEffect(() => {
console.log("num2 update: ", num2);
}, [num2]);
return (
<div>
<div>num: {num}</div>
<div>
<button onClick={() => setNum(num + 1)}>加 1</button>
<button onClick={() => setNum(num - 1)}>减 1</button>
</div>
</div>
);
}
⚠️ useEffect Первая функция обратного вызова может возвращать функцию для уничтожения побочных эффектов, что эквивалентно размонтированию жизненного цикла компонента Class. Для удобства описания здесь не выполняется реализация.
В этом разделе я попытаюсь ответить на два вопроса «принцип реализации 🤔️ useEffect» и «сценарий применения 🤔️ useEffect».
Class VS Hooks
Хотя Хуки выглядят круче и лаконичнее. Но в реальной разработке я предпочитаю использовать класс для объявления компонентов. Сравнение двух методов выглядит следующим образом:
Class | Hooks |
---|---|
Логика кода понятна (конструктор, componentDidMount и т.д.) | Необходимо сопоставить имена переменных и комментарии |
Не склонен к утечкам памяти | склонны к утечкам памяти |
В целом хуки предъявляют более высокие требования к написанию кода, и в отсутствие эффективного механизма, обеспечивающего читабельность кода и избегающего рисков, я по-прежнему выбираю Class. По поводу утечек памяти, вот пример (нет возможности обойти эту практику передачи функции обновления состояния в глобал):
import React, { useState } from "react";
import ReactDOM from "react-dom";
let func: any;
setInterval(() => {
typeof func === "function" && func(Date.now());
console.log("interval");
}, 1000);
function App() {
const [num, setNum] = useState < number > 0;
if (typeof func !== "function") {
func = setNum;
}
return <div>{num}</div>;
}
function render() {
ReactDOM.render(<App />, document.getElementById("root"));
}
render();
Ссылка на ссылку
- React hooks: not magic, just arrays
- A Complete Guide to useEffect
- Полное руководство по использованию эффектов
-
Принцип React Hooks:
useEffect
Есть проблема с реализацией, callback-функция вызывает функцию обновления состояния, которая взорвет стек.
В статье много неуместных мнений, приглашаем к обсуждению и исправлению.