Автор оригинала: Lev Izraelit.
Вступление
Я чувствовал непросто в первый раз, когда я читал о крючки в реакции. Их внутренняя работа казалась слишком волшебной. Я помню, как смотреть на простой пример и пытаясь понять, как он работал под капотом:
function Counter() { const [count, setCount] = useState(0) return (The count is: {count}) }
Было ясно, что был примером делать Отказ Вы нажимаете на + Кнопка, и количество увеличивается. Но где была ценность Считать
Будучи хранятся и как мы получили правильное значение, хотя 0
был передан каждый раз? Даже когда я начал включать в себя крючки в мои приложения, у меня было несколько четких ответов. Поэтому я начал поиск источников, которые описали, как крючки работают под капотом. Наконец, я решил попробовать и переосмыслить некоторые основные крючки.
Этот пост подробно описывает мой процесс переосмысления Уместите крюк. Для меня целью никогда не было точно соответствовать реальной реализации. Цель состояла в том, чтобы получить некоторое представление о том, как некоторые, как Уместите
может быть реализованным.
Классы и штат
Вообще говоря, Государство Включает в себя любое значение, которое меняется со временем, когда эта стоимость необходимо запомнить программой. Для компонентов React Class концепция государства переводится непосредственно в Государство
объект. Идея состоит в том, чтобы инкапсулировать все (или, по крайней мере, большинство) изменяющихся значений в одном месте. Мы инициализировали Государство
Объект с некоторыми значениями по умолчанию, когда класс создан, а затем измените эти значения косвенно, вызывая SetState
Метод:
class Counter extends React.Component { constructor() { this.state = { count: 0 } } increment = () => { this.setState({ count: this.state.count + 1 }) } render() { return ( <>count: {this.state.count}) } }
SetState
Метод воссоздает состояние компонента, объединяя существующее состояние с новым объектом, который был передан как аргумент. Если бы мы использовали базу SetState , это будет выглядеть что-то подобное:
setState(newPartialState) { this.state = { ...this.state, ...newPartialState } // rerender the component }
Функции и состояние
В отличие от объекта или класса, функция не может внутренне поддерживать состояние. Это причина в реакции, что функциональный компонент также называется A без гражданства Функциональный компонент. Поэтому я бы ожидал, что функциональный компонент работает так же, как простой Добавить Функция – Учитывая тот же вход, я бы ожидал, чтобы всегда получить тот же выход. Если бы мне нужно было государству, мне придется создать компонент родительского класса и иметь что Компонент пропустите состояние:
// The Counter functional component will receive // the count and a setCount function // from a parent class component const Counter = ({ count, setCount }) => ( <>count: {count}) class CounterContainer extends React.Component { // shorthand for using a constructor state = { count: 0 } setCount = (newCount) => { this.setState({ count: newCount }) } render() { return () } }
В некотором смысле Уместите
Крюк дает нам путь к Скажите Реагируйте, что нам нужно что-то вроде этого компонента родительского класса, без необходимости создавать его. Мы просто говорим, что мы хотим использовать Состояние и реагирование создаст это состояние для нас.
Функции, которые используют состояние
В качестве первой попытки создать компонент родительского класса, мы могли бы попытаться получить функциональный компонент непосредственно изменить глобальную переменную:
let count = 0; const Counter = () => ( <>{count})
Это, однако, не совсем работает. Несмотря на то, что ценность Считать
меняется, Счетчик
Компонент не восстанавливает, чтобы показать новое значение. У нас есть что-то похожее на SetState
Вызов, который бы перерезал компонент при стоимости Считать
изменения. Мы можем сделать setcount
Функция, которая делает только что:
let count = 0 function setCount(newCount) { count = newCount ReactDOM.render() } const Counter = () => ( <> {count})
Это работает! Для обеспечения Считать
и setcount
Всегда используются вместе, мы можем поставить их внутри объекта. Давайте назовем этот объект MyReact
:
const MyReact = { count: 0, setCount(newCount) { this.count = newCount; ReactDOM.render() } }
Чтобы сделать вещи даже яснее, давайте создадим Установка
Функция, которая возвращает объект с Считать
и setcount
:
useCount() { return { count: this.count, setCount: this.setCount } }
Далее мы хотели бы позволить абонента Установка
пройти начальное значение. Это представило нам проблему. Нам нужно только настроить первоначальное значение в первый раз, когда Установка
называется. На любой последующий звонок мы хотели бы использовать существующее значение Установка
Отказ Одно решение добавляет УсловныйИсициализирован
Переменная. Мы изначально настроим это на ложь
и установите это на правда
В первый раз, когда Установка
называется:
stateInitialized: false, useCount(initialValue) { if (!this.stateInitialized) { this.count = initialValue; this.stateInitialized = true; } // ... }
Теперь, когда мы получили основы, работаем, мы можем сделать MyReact
Больше общего переименования Считать
Переменная к Государство
и имена методов для Уместите
и SetState
Отказ Кроме того, мы вернемся Государство
и SetState
В массиве, чтобы позволить легко переименовать:
const MyReact = { state: null, stateInitialized: false, setState(newState) { this.state = newState; ReactDOM.render(, rootElement); }, useState(initialValue) { if (!this.stateInitialized) { this.stateInitialized = true; this.state = initialValue; } return [this.state, this.setState]; } }; const Counter = () => { const [count, setCount] = MyReact.useState(0) // ... }
Мы также можем добавить оказывать
Метод MyReact
и назовите этот метод вместо того, чтобы звонить Reactom.runder
Отказ Это позволит нам сохранить Счетчик
Компонент как часть MyReact
:
// ... setState(newState) { this.state = newState; ReactDOM.render(, this.rootElement); }, // ... render(component, rootElement) { this.component = component; this.rootElement = rootElement; ReactDOM.render( , this.rootElement); } // .. // later MyReact.render(Counter)
Многократные переменные состояния
Следующий шаг – включить MyReact
управлять несколькими переменными. Первый шаг – заменить один Государство
Переменная с массивом из государственных переменных. Теперь нам понадобится какой-то способ узнать, каждый раз SetState
называется, который Переменная состояния – это тот, который необходимо изменить. Мы можем достичь этого, полагаясь на призыв к поручению Уместите
Отказ Возьмите, например, два последующих вызове ниже:
const MyCounter = () => { const [count, setCount] = MyReact.useState(0); const [name, setName] = MyReact.useState(""); }
MyReact.Usestate
Методы всегда будут выполняться в том же порядке, сначала возвращая значения Count1
, setcount1
, а затем возвращение ценностей Имя
, setname
Отказ Это будет так, пока MyReact.Usestate
это не Вызывается внутри условного блока, где условие не всегда верно или ложно.
Теперь, поскольку у нас есть два или более переменных состояний, каждая переменная состояния должна иметь соответствующую SetState
метод. Мы можем достичь этого, используя массив объектов, где объект хранит Государство
ценность и соответствующий SetState
метод. Мы можем позвонить каждому из объектов A государственная страница
и массивы, которые удерживают их statearray
Отказ
[{ value: count, setCount }, { value: name, setName }, ...]
Теперь нам нужен способ отслеживать, какой элемент массива используется в любой момент времени. Например, наличие двух звонков на MyReact.Usestate
Выше первый звонок должен вернуть [Считать, setcount]
а второй звонок должен вернуться [Имя, setname]
Отказ Мы можем использовать переменную для отслеживания этого значения. Давайте назовем эту переменную currentstateindex
Отказ
currentstateindex
будет сброшен до 0
всякий раз, когда любой SetState
называется. Когда значение currentstateindex
становится равным длине массива, мы создадим новую пару Государство
SetState
Отказ
const MyReact = { stateArr: [], currentStateIndex: 0, component: null, useState(initialValue) { // if we reached beyond the last element of the array // We will need create a new state if (this.currentStateIndex === this.stateArr.length) { const statePair = { value: initialValue, setState(newValue) { statePair.value = newValue; MyReact.currentStateIndex = 0; ReactDOM.render(, rootElement); } }; this.stateArr.push(statePair); } // get the current state and setState before incrementing the index const currentStatePair = this.stateArr[this.currentStateIndex]; this.currentStateIndex += 1; return [currentStatePair.value, currentStatePair.setState]; }, render(component, rootElement) { this.component = component; this.rootElement = rootElement; ReactDOM.render( , this.rootElement); } };
Пример
Учитывая вышеуказанную реализацию, давайте попробуем привести пример компонента, который использует две переменные состояния:
const Counter = () => { const [count1, setCount1] = MyReact.useState(0); const [count2, setCount2] = MyReact.useState(0); return ( <>The first count is: {count1}The second count is: {count2}) } MyReact.render(Counter)
Вы можете найти песочницу с MyReact
и Счетчик
Компонент по нажав на эту ссылку
После кода в песочнице они будут начальными значениями Реагировать
:
MyReact { stateArr: [], currentStateIndex: 0, component: null, }
После первый звонок в Уместите
:
const Counter = () => { const [count1, setCount1] = MyReact.useState(0); // <--
Значения MyReact
будет:
MyReact { stateArr: [{ value: 0, setState: fn() }], currentStateIndex: 1, component: Counter, }
После Второй звонок в Уместите
:
const Counter = () => { const [count1, setCount1] = MyReact.useState(0); const [count2, setCount2] = MyReact.useState(0); // <--
Значения MyReact
будет:
MyReact { stateArr: [{ value: 0, setState: fn() }, { value: 0, setState: fn() }], currentStateIndex: 2, component: Counter, }
Теперь, если первая + Кнопка нажата, значения MyReact
станет:
MyReact { stateArr: [{ value: 1, setState: fn() }, { value: 0, setState: fn() }], currentStateIndex: 0, component: Counter, }
Что приведет к Счетчик
Быть оказанным снова. На последующие звонки на Уместите
, только currentstateindex
будет увеличиваться, а существующие элементы statearr
будет возвращено.
Заключение
Итак, мы приехали на что-то довольно похожее на актестатный крючок реагирования. Я не могу сказать, если понимание внутренних работ крючков сделает кому-то лучший разработчик реагирования. Но я чувствую, что стоит попробовать попытаться понять, как могут быть созданы абстракции – это может помочь нам понять те, которые уже были сделаны, и сделать новые абстракции наших собственных.