Супер сексуальные React Hooks (2) Снова поговорим о замыканиях

React.js

再谈闭包

Если вы действительно не понимаете его в течение дня, вы должны продолжать учить его.

Когда я проходил собеседование при приеме на работу, я ненавидел людей, которые спрашивали меня о закрытии, потому что я не мог ответить. Сейчас я беру интервью у других, но я люблю спрашивать о замыканиях, потому что замыкания действительно могут напрямую проверить глубину вашего понимания JS. На самом деле очень мало людей, которые могут на него ответить.

За последние два года я опросил более 200 человек, и тот, у кого самые сильные технические способности, — это толстый приятель Али P6, которого здесь называют PP. Основа JS у PP очень прочная, а понимание React относительно глубоко Мы хорошо провели время, поболтав о других проблемах. Но даже у такого мастера есть сложности с замыканиями, и он с первого раза не дал ответ, который я хотел.

Поэтому, если есть такая статья или две, это может помочь всем понять замыкание, я думаю, что это очень замечательная вещь. В продвинутой серии статей по основам JS я поделился с вами основами, определением, характеристиками замыканий и способами наблюдения за замыканиями в браузере Chrome.Эта статья посвящена практике и продолжает учиться.

В качестве вступления возьмем разговор между мной и одноклассниками ПП в процессе собеседования, содержание разговора примерно следующее:

Я: Можешь рассказать о своем понимании замыканий? PP: Когда функция выполняется, к переменным в верхней области можно получить доступ для формирования замыкания, а замыкание может сохранять переменные.

Я: Есть что-нибудь еще? ПП: ушел

Я: Если я скажу, что замыкания используются почти везде в нашей практике, согласны ли вы с этим утверждением? ПП (немного колеблясь): согласен

Я: Какие сцены задействованы? ПП: Какое-то время не могу вспомнить.

Я (не желая быть готовым, продолжая загружаться): Модульность, которую вы должны знать, как вы думаете, есть ли возможность модуля и замыкания? ПП: нет

Я: Ты уверен? ПП: Конечно нет!

Хорошо, вот, если вы интервьюер, что вы думаете об ответе ПП? Вы выполнили свои требования?

Конечно, студенты, купившие мою книгу и внимательно прочитавшие ее, должны знать, что ответ неудовлетворителен. Здесь мы объединим реальную ситуацию с React Hooks, а затем поговорим на эту тему.

Может быть, некоторым студентам это покажется странным.Эта серия статей, очевидно, является введением в React Hooks, который имеет полуцентовое отношение к замыканиям?

Вместо этого замыкания лежат в основе React Hooks. Без понимания замыканий использование React Hooks не может достичь уровня совершенства. Если вы используете его только поверхностно, вы можете просто прочитать официальную документацию, что не является целью нашей серии статей.

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

Закрытие - специальный объект Он состоит из двух частей: контекста выполнения A и функции B, созданной в A..

Когда B выполняется, если к объекту переменной в A обращаются, тогда замыкание будет сгенерировано.

В большинстве толкований, включая многие известные книги и статьи, имя функции B относится к сгенерированному здесь замыканию. В chrome на замыкание ссылается имя функции контекста выполнения A.

Многие места любят использовать лексическую среду или лексику, чтобы определить концепцию закрытия, но замыкания - это специальные объекты, которые генерируются во время выполнения кода, поэтому я думаю, что это более точнее, чтобы использовать контекст выполнения. Конечно, это не влияет на понимание и использование закрытий.

По сути, в JavaScript нет собственной концепции модулей, мы можем использовать функции/самоисполняемые функции только для имитации модулей.

В текущем интерфейсном проекте (спецификация синтаксиса модулей ES6) используемые модули по сути являются функциями или самовыполняющимися функциями.

Инструменты упаковки, такие как webpack, помогут нам упаковать его в функцию.

Подумайте об этом, определите компонент React и используйте его в других модулях, имеет ли это какое-то отношение к замыканиям? Давайте попробуем простой пример анализа.

Определите компонент счетчика в модуле Counter.jsx.

// Counter.jsx
export default function Counter() {}

Затем используйте компонент Counter в модуле App.

// App.jsx
import Counter from './Counter';
export default function App() {
 // todo
   return (
    <Counter />
  )
}

Сочетая вышеуказанные знания, учащиеся с прочной базой должны знать ответ здесь.

Приведенный выше код мы можем вручную преобразовать в псевдокод.

const CounterModule = (function() {
  return function Counter() {}
})()

const AppModule = (function() {
  const Counter = CounterModule;
  return function App() {
    return Counter();
  }
})()

Давайте заменим определения A и B замыкания выше именами в этом примере:

Самовыполняющаяся функция AppModule и функция App, созданная в AppModule.

Когда приложение выполняется в рендере, осуществляется доступ к объекту переменной в AppModule (определяется переменная Counter), затем будет сгенерировано замыкание.

Поэтому связь между замыканиями и модулями здесь очень ясна. В соответствии с условиями генерации и практическими сценариями замыканий мы обнаружим, что генерировать замыкания в модулях очень просто.Каждый модуль JS можно считать отдельной областью. Когда код выполнен, лексическая область создает контекст. Если вы создаете функцию внешнего эталонного доступа, он создается в модуле. Условия, доступа к функции. Переменные внутри модуля снаружи, замыкание генерируется.

Другой пример.

Определите модуль с именем State, код выглядит следующим образом:

// state.js
let state = null;

export const useState = (value: number) => {
  // 第一次调用时没有初始值,因此使用传入的初始值赋值
  state = state || value;

  function dispatch(newValue) {
    state = newValue;
    // 假设此方法能触发页面渲染
    render();
  }

  return [state, dispatch];
}

Введен и используется в других модулях.

import React from 'react';
import {useState} from './state';

function Demo() {
  // 使用数组解构的方式,定义变量
  const [counter, setCounter] = useState(0);

  return (
    <div onClick={() => setCounter(counter + 1)}>hello world, {counter}</div>
  )
}

export default Demo();

Состояние контекста выполнения (состояние модуля) и функция useState, созданная в состоянии

Когда useState выполняется в Demo, осуществляется доступ к объекту переменной в состоянии, затем будет сгенерировано закрытие.

Вопрос для размышления: вызовет ли выполнение setCounter замыкание?

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

Вот как React Hooks позволяют функциональным компонентам иметь внутреннее состояние.

Принцип реализации и использование useState в данном случае в основном такие же, как и у React Hooks. Но реальная реализация исходного кода точно не такая простая и грубая.

Кратко разберем, как реализован исходный код React Hooks.

Следует отметить, что основное внимание в нашем анализе исходного кода здесь уделяется пониманию роли замыканий в React Hooks. Если вы хотите добиться большего прогресса, вам нужно понять принцип волокна, и я поделюсь им с вами позже.

Еще один момент, достойный всеобщего внимания, — сознательно обобщать свои мысли в процессе чтения исходного кода, что будет большим подспорьем, когда вы захотите прочитать чужой код. Не буду писать метод напрямую, подробностями поделюсь позже.

С помощью отладки точек останова обнаруживается, что все виды логической обработки React Hooks находятся в затруднительном положении.ReactCurrentDispatcherэтот модуль.

Этот файл содержит более 2000 строк и представляет собой очень сложный модуль.

Первый шаг — выяснить, что делает этот модуль.

Конкретный метод заключается в наблюдении за тем, что возвращает модуль.поиск экспорта. export указывает, что этот модуль будет выброшен за пределы интерфейса, это единственный способ связи с внешним модулем.

搜索export

Результаты поиска показали, что большая часть экспорта — это объявления типов, на которые мы здесь не обращаем внимания. После несложного анализа вся основная логика написана наrenderWithHooksсередина. Этот метод также можно найти с помощью отладки точки останова.

Быстро проанализируйте роль функции. Одна идея — посмотреть, что она возвращает, а вторая идея — посмотреть, что она изменяет..

Анализ показывает, что функция изменяет переменные во внешней области видимости, что является важным сообщением, которое нам нужно.

image.png

до отReactHooks.jsВ модуле найдено, что реализация USESTATE очень проста, как следует

export function useState<S>(initialState: (() => S) | S) {
  const dispatcher = resolveDispatcher();
  return dispatcher.useState(initialState);
}

Продолжить просмотр реализации Resolvedispatcher

function resolveDispatcher() {
  const dispatcher = ReactCurrentDispatcher.current;
  return dispatcher;
}

На данный момент это в основном правильное число. Конечно, конкретные принципы необходимо понимать в сочетании с планированием оптоволокна, и мы не будем здесь углубляться. В этой статье мы по-прежнему сосредоточены на замыканиях.

Как известно из приведенного выше рисунка, при определенных условиях (при обновлении)ReactCurrentDispatcher.currentто естьHooksDispatcherOnUpdateInDEV, этот метод находится вReactFiberHooksобъявлено в модуле.

Продолжайте читать исходный код и узнайтеHooksDispatcherOnUpdateInDEVпеременная, определенная в этом модуле.

image.png

В это время мы, естественно, должны подумать: «О, здесь используются замыкания».

Продолжайте использовать ключевое слово и обнаружите, что переменной присвоено определенное значение. Это все API, поддерживаемые ReactHooks. Как показано

image.png

Давайте пока сосредоточимся на useState и посмотрим, как он реализован.

useState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  currentHookNameInDev = 'useState';
  updateHookTypesDev();
  const prevDispatcher = ReactCurrentDispatcher.current;
  ReactCurrentDispatcher.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
  try {
    return updateState(initialState);
  } finally {
    ReactCurrentDispatcher.current = prevDispatcher;
  }
},

Ключ вотupdateState(initialState)

function updateState<S>(
  initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
  return updateReducer(basicStateReducer, (initialState: any));
}

продолжай искатьupdateReducer, логика updateReducer сложнее. Однако, основываясь на двух идеях, упомянутых выше, мы можем быстро разобраться, увидев, что он изменил, а что вернул.

function updateReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const hook = updateWorkInProgressHook();
  const queue = hook.queue;
  // ...
  queue.lastRenderedReducer = reducer;

  if (numberOfReRenders > 0) {
    // This is a re-render. Apply the new render phase updates to the previous
    // work-in-progress hook.
    const dispatch: Dispatch<A> = (queue.dispatch: any);
    if (renderPhaseUpdates !== null) {
      // ...
    return [hook.memoizedState, dispatch];
  }

  // The last update in the entire queue
  const last = queue.last;
  // The last update that is part of the base state.
  const baseUpdate = hook.baseUpdate;
  const baseState = hook.baseState;

  // Find the first unprocessed update.
  let first;
  if (baseUpdate !== null) {
    if (last !== null) {
      // For the first update, the queue is a circular linked list where
      // `queue.last.next = queue.first`. Once the first update commits, and
      // the `baseUpdate` is no longer empty, we can unravel the list.
      last.next = null;
    }
    first = baseUpdate.next;
  } else {
    first = last !== null ? last.next : null;
  }
  if (first !== null) {
    // ...

    hook.memoizedState = newState;
    hook.baseUpdate = newBaseUpdate;
    hook.baseState = newBaseState;

    queue.lastRenderedState = newState;
  }

  const dispatch: Dispatch<A> = (queue.dispatch: any);
  return [hook.memoizedState, dispatch];
}

Упростите исходный код и обнаружите, что, хотя логика сложна, две основные вещи по-прежнему заключаются в изменении вызываемогоhookпеременные и возвращает[hook.memoizedState, dispatch].

Что это за крючок? существуетupdateWorkInProgressHookОн находится в методе, который содержит хукmemoizedState, baseState, queue, baseUpdate, nextОбъект свойств.

function updateWorkInProgressHook(): Hook {
  if (nextWorkInProgressHook !== null) {
    workInProgressHook = nextWorkInProgressHook;
    nextWorkInProgressHook = workInProgressHook.next;

    currentHook = nextCurrentHook;
    nextCurrentHook = currentHook !== null ? currentHook.next : null;
  } else {
    invariant(
      nextCurrentHook !== null,
      'Rendered more hooks than during the previous render.',
    );
    currentHook = nextCurrentHook;

    const newHook: Hook = {
      memoizedState: currentHook.memoizedState,

      baseState: currentHook.baseState,
      queue: currentHook.queue,
      baseUpdate: currentHook.baseUpdate,

      next: null,
    };

    if (workInProgressHook === null) {
      workInProgressHook = firstWorkInProgressHook = newHook;
    } else {
      workInProgressHook = workInProgressHook.next = newHook;
    }
    nextCurrentHook = currentHook.next;
  }
  return workInProgressHook;
}

updateReducerВ возвращаемом массиве первое значение равноmemoizedState.

Следовательно, можно сделать вывод, что наше состояние на самом деле кэшируется вhook.memoizedStateв этом значении.

Продолжайте наблюдатьupdateWorkInProgressHookобнаружено, что этот метод внутренне изменяет многие внешние переменные,workInProgressHook,nextWorkInProgressHook,currentHookЖдать. а такжеmemoizedState: currentHook.memoizedState.

Поэтому в итоге наше состояние при обновлении реально существует вcurrentHook. Это также использует замыкания.

Хорошо, согласно этой идее, логику исходного кода React Hooks можно быстро проанализировать, но здесь мы сосредоточимся на том, как замыкания играют роль в React Hooks. Если вы уже испытали на себе роль замыканий, цель этой статьи в основном достигнута.

Следует отметить, что при обновлении происходит вызовupdateReducer, но во время инициализации вызывается другой метод, как показано на рисунке.

image.png

Закрытия повсюду, вы должны осознать истинный смысл этого предложения.

Чтение исходного кода не является обязательным процессом в процессе обучения.Если основа JS недостаточно прочна, не беспокойтесь о том, что делать, если вы не можете прочитать ее самостоятельно. Просто делай это медленно.

Все кейсы из этой серии статей можно посмотреть по следующему адресу

GitHub.com/advance-вместе…

Эта серия статей является оригинальной, пожалуйста, не перепечатывайте без разрешения, пожалуйста, не забудьте отправить мне личное сообщение