Эта статья полностью понимает принцип и реализацию хуков реакции.

React.js

Эта статья взята из«Одна статья полностью понимает принцип и реализацию реагирующих хуков», если вы чувствуете себя хорошо, добро пожаловать вРепозиторий на гитхабезвезда.

Резюме

При крючке характерных компонентов времени приготовления всегда чувствую его простоту и удобство. Конечно, «нет бесплатного обеда», оно за счет читаемости, и есть риск утечки памяти (последний упомянутый). Но это не мешает исследовать свою магию.

Прежде чем начать, я надеюсь, что вы прочитали документацию по хуку или использовали его в проекте. Но если вас интересует только функциональное программирование, вы можете что-то выиграть.

Для того, чтобы писать было более плавно, планирую сначала закинуть несколько вопросов, которые постепенно будут решаться в процессе реализации исходников:

  • 🤔️ Принцип реализации useState
  • 🤔️ Почему нельзя использовать хук внутри цикла и суждение
  • 🤔️ Принцип реализации useEffect
  • 🤔️ Сценарии применения UseEffect
  • 🤔️Class vs Hooks

⚠️ Все коды предоставлены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();

Ссылка на ссылку

В статье много неуместных мнений, приглашаем к обсуждению и исправлению.