Автор: Чан Фэн
Впервые я услышал о React Hooks в те дни, когда он только вышел, и в Интернете было много статей, рассказывающих о нем. Видя, что это буквально означает «крюк React», предполагается, что это должен быть хук, который модифицирует компоненты React. React расширяет множество концепций, компонентов более высокого порядка, функционала, свойств рендеринга, контекста и так далее. А вот и новая концепция, разработка интерфейса достаточно сложна! Я использую Vue последние два года и чувствую, что многие функции, связанные с React, имеют аналогичные решения в Vue, поэтому я не сразу это понял.
Позже я увидел Ю ДаНедавний прогресс Vue 3.0Hooks API также упоминается в видео и написанPOC с использованием хуков во Vue. Похоже, что хуки по-прежнему очень важны, поэтому я сразу же нашел официальную документацию по хукам React и видео с конференции — очередной раунд плохих исправлений.
Прочитав соответствующую информацию, я чувствую, что перспективы применения хуков довольно привлекательны, и они решают многие болевые точки в текущей разработке интерфейса. Однако React Hooks все еще находится в стадии альфы, он не идеален, а встроенные хуки небогаты. А у Vue есть только Hooks POC, Vue3.0 скорее всего добавят, но это займет еще несколько месяцев. Поэтому не рекомендуется использовать его в официальном коде.
В этой статье основное внимание уделяется объяснению моего понимания хуков и реализации исходного кода API хуков в Vue. Также обратите внимание, что хуки — это нейтральная концепция, которую можно использовать в любом фреймворке, не только в React :).
Какие проблемы решают хуки
Прежде чем мы начнем, давайте вспомним, с какими проблемами нам помогают хуки.
согласно сзаявление Дэна, При разработке проекта React были обнаружены следующие болевые точки:
- Многокомпонентный код, который трудно использовать повторно.
- Крупные компоненты, сложные в обслуживании.
- Иерархия дерева компонентов, часто глубоко вложенная.
- Компоненты класса, не легко понять.
Конечно, то же самое верно и для проектов Vue, и эти проблемы на самом деле связаны.
В качестве способа разработки мы разбиваем страницу в разные компоненты, нажимайте поток данных, гнездо. Минимальные частицы структуры кода являются компонентами.
Если какие-то компоненты слишком велики, мы продолжаем разбивать на более мелкие компоненты и вызывать их в родительском компоненте. Если между несколькими компонентами много универсальной логики, мы используем Mixin или систему наследования компонентов сборки.
Проблема в том, что разделение компонентов позволяет нам случайно углубить уровень компонентов, что увеличивает сложность системы, а также может повлиять на производительность. Кроме того, логика взаимодействия некоторых компонентов действительно сложна и не может быть разделена, система давно итерируется, а накопленный код очень большой, связанная логика разбросана по разным жизненным циклам компонентов, что сложно поддерживать.
Повторное использование логики в компонентах сложнее! миксины — палка о двух концах (ссылка:миксины вредны); наследование компонентов также нежелательно. Хотя наследование хорошо работает в строго типизированных объектно-ориентированных языках (таких как Java/C#), оно всегда кажется неадекватным в JavaScript, что также делает код неясным и трудным для понимания; Пакет util также является обычной практикой, но если извлекаемая общая логика должна быть связана с локальным состоянием компонента, и если связанная общая логика должна быть разбросана по разным жизненным циклам компонента, она будет быть невозможным! На этом этапе мы склонны идти на компромисс - возникают большие компоненты/повторяющаяся логика.
Компоненты классов также заставляют людей любить и ненавидеть. Мы всегда выступали за абстрагирование структуры кода объектно-ориентированным способом. Пока нет лучшего решения, это действительно хороший выбор. Но лично мне кажется, что в JavaScript, особенно в разработке на основе системы компонентов React/Vue, он не очень подходит. Нам часто нужны хитрые способы, чтобы типы JavaScript поддерживали члены super, private и тщательно обрабатывали указатель this в функциях. Благодаря гибкости и выразительной силе JavaScript вы всегда сможете найти правильный способ написания кода. Вопрос в том, как поддерживать такой код, мы хотим, чтобы код был кратким и ясным, соответствовал общепринятому письму, а не непонятным минным полям. Просто говоря об этом, мы знаем, что JavaScript основан на статической области видимости, то есть из исходного кода мы можем вывести области видимости переменных, но это исключение, он основан на динамической области видимости, то есть на значении это называется определяемым лицом. Один и тот же метод вызывается по-разному, и эта точка у него совершенно разная, поэтому нам приходится много использовать bind, чтобы обеспечить эту точку.
О JavaScript в этом использовании заинтересованные студенты могут обратиться к:Объясните это подробно.
Как решить эти болевые точки -- Хуки !
Что такое крючки
википедия оhooksОпределение:
The term hooking covers a range of techniques used to alter or augment the behavior of an operating system, of applications, or of other software components by intercepting function calls or messages or events passed between software components. Code that handles such intercepted function calls, events or messages is called a hook.
В переводе на китайский язык это означает: хуки включают ряд технологий, используемых для изменения или улучшения поведения операционных систем, приложений и программных компонентов. Эти технологии реализуются путем перехвата вызовов функций, сообщений и событий во время работы программного обеспечения.
Другими словами, с помощью хуков мы можем позже изменить или улучшить поведение существующей системы во время выполнения. Затем, в соответствии с системой компонентов React/Vue, хуки — это модули кода, которые могут изменить или улучшить поведение компонентов во время выполнения.
Читая техническую документацию по React Hooks, это так. React предоставляет два важных встроенных хука:
- useState — добавляет к компоненту собственное реактивное состояние.
- useEffect — логика побочного эффекта, которую необходимо выполнить после добавления обновления состояния в компонент.
React также предоставляет некоторые другие встроенные хуки, связанные с функциями компонента, такие как useContext, useReducer, useMemo, useRef и т. д. В будущем должно быть больше встроенных хуков, затрагивающих все аспекты среды выполнения компонента. Мы также можем реализовать собственные хуки на основе этих встроенных хуков.
React подчеркивает, что хуки можно использовать только в функциональных компонентах. Функциональный компонент — это, по сути, простая функция рендеринга без сохранения состояния, а данные поступают из внешних источников. Так как же добавить к компонентам локальное состояние, а также различную бизнес-логику, связанную с жизненным циклом? Ответ: через хуки. Команда React надеется, что «функциональные компоненты + хуки» станут основным способом разработки компонентов в будущем, поэтому хуки должны иметь возможность вторгаться во все звенья жизненного цикла компонента, чтобы добавлять состояние и поведение компонента. Хотя хуки, предоставляемые React, в настоящее время недостаточно богаты, в будущем они будут постепенно улучшаться.
Подводя итог, мы обнаружили, что хуки могут сделать нашу модульную разработку более детализированной и функциональной. Функция компонента становится небольшой сборкой с помощью хуков. Эта функция также решает четыре проблемы, упомянутые выше: повторное использование кода, большие компоненты, слишком глубокое дерево компонентов и проблемы компонентов класса.
Для получения дополнительной информации и многих примеров React Hooks см.:Introducing Hooks
Для Vue, в дополнение к useState, useEffect, useRef, совместимым с API React Hooks, вы также можете реализовать встроенные хуки, такие как useComputed, useMounted, useUpdated, useWatch и т. д., чтобы вы могли добавлять функции к компонентам более подробно. .
Vue-реализация Hooks API
Проанализируйте это здесьОтличные хуки POC VueРеализация исходного кода , чтобы углубить понимание Hooks API.
withHooks
Мы знаем, что реактивный крюк можно использовать только в функциональном компоненте, а Vue также определен.
Сводятся Vue для упаковки версии «функциональные компоненты» в функциональных компонентах, вы можете использовать API крючков.
Пример использования withHooks:
import { withHooks, useData, useComputed } from "vue-hooks"
const Foo = withHooks(h => {
const data = useData({
count: 0
})
const double = useComputed(() => data.count * 2)
return h('div', [
h('div', `count is ${data.count}`),
h('div', `double count is ${double}`),
h('button', { on: { click: () => {
data.count++
}}}, 'count++')
])
})
В коде withHooks оборачивает функциональный компонент (функцию рендеринга) и добавляет локальные данные состояния и вычисляемое свойство double к компоненту с помощью Hooks.
Примечание: useData в коде аналогичен useState, описанному ниже.
Детали реализации withHooks:
let currentInstance = null
let isMounting = false
let callIndex = 0
function ensureCurrentInstance() {
if (!currentInstance) {
throw new Error(
`invalid hooks call: hooks can only be called in a function passed to withHooks.`
)
}
}
export function withHooks(render) {
return {
data() {
return {
_state: {}
}
},
created() {
this._effectStore = {}
this._refsStore = {}
this._computedStore = {}
},
render(h) {
callIndex = 0
currentInstance = this
isMounting = !this._vnode
const ret = render(h, this.$attrs, this.$props)
currentInstance = null
return ret
}
}
}
В коде:
withHooks добавляет в компонент частное локальное состояние _state для хранения значений состояния, связанных с useState и useData.
При создании некоторые объекты класса хранилища, необходимые для поддержки хуков ( useEffect, useRef, useComputed ), внедряются в компонент.
Дело в функции рендеринга в коде:
- callIndex, который предоставляет ключ для объектов хранилища, связанных с хуками. Здесь каждый рендеринг сбрасывается на 0, чтобы соответствующие хуки могли сопоставляться в соответствии с порядком вызова. Эта обработка также ограничивает вызов хуков только в коде верхнего уровня.
- currentInstance в сочетании с функцией sureCurrentInstance используется для того, чтобы хуки можно было использовать только в функциональных компонентах.
- isMounting, используется для определения состояния монтажа компонента.
useState
useState используется для добавления реактивного локального состояния к компоненту и средства обновления, связанного с этим состоянием.
Сигнатура метода:
const [state, setState] = useState(initialState);
setState используется для обновления состояния:
setState(newState);
Пример использования useState:
import { withHooks, useState } from "vue-hooks"
const Foo = withHooks(h => {
const [count, setCount] = useState(0)
return h("div", [
h("span", `count is: ${count}`),
h("button", { on: { click: () => setCount(count + 1) } }, "+" )
])
})
В коде к компоненту через useState добавляются локальный счетчик состояний и функция setCount для обновления значения состояния.
Детали реализации useState:
export function useState(initial) {
ensureCurrentInstance()
const id = ++callIndex
// 获取组件实例的本地状态。
const state = currentInstance.$data._state
// 本地状态更新器,以自增id为键值,存储到本地状态中。
const updater = newValue => {
state[id] = newValue
}
if (isMounting) {
// 通过$set保证其是响应式状态。
currentInstance.$set(state, id, initial)
}
// 返回响应式状态与更新器。
return [state[id], updater]
}
Приведенный выше код четко описывает, что useState создает локальное реактивное состояние в компоненте и генерирует средство обновления состояния.
нужно знать, это:
- Функция sureCurrentInstance заключается в том, чтобы убедиться, что useState должен выполняться в рендере, то есть он должен выполняться в функциональных компонентах.
- Используйте автоматически увеличивающийся идентификатор, сгенерированный callIndex, в качестве ключа для хранения значения состояния. Обратите внимание, что useState должен полагаться на последовательность вызовов первого рендеринга, чтобы соответствовать прошлому состоянию (callIndex должен сбрасываться на 0 при каждом рендеринге). Это также ограничивает использование useState в коде верхнего уровня.
- Другие крючки также должны следовать двум вышеуказанным пунктам.
useEffect
useEffect используется для добавления логики побочных эффектов, которую необходимо выполнить после обновления состояния компонента.
Подпись метода:
void useEffect(rawEffect, deps)
Логика побочного эффекта, указанная в useEffect, будет выполняться один раз после монтирования компонента, выборочно выполняться после рендеринга каждого компонента в соответствии с указанными зависимостями, а логика очистки будет выполняться при размонтировании компонента (если указано).
Пример вызова 1:
import { withHooks, useState, useEffect } from "vue-hooks"
const Foo = withHooks(h => {
const [count, setCount] = useState(0)
useEffect(() => {
document.title = "count is " + count
})
return h("div", [
h("span", `count is: ${count}`),
h("button", { on: { click: () => setCount(count + 1) } }, "+" )
])
})
Код, сделав использование значения изменений значения каждый раз, когда подсчет, сбрасывает Document.title.
Примечание. Второй параметр deps для useEffect здесь не указан, что означает, что логика, указанная с помощью useEffect, будет выполняться до тех пор, пока компонент повторно отображается, и нет ограничений на количество изменений счетчика.
Подробное описание параметра useEffect см.:Using the Effect Hook
Пример вызова 2:
import { withHooks, useState, useEffect } from "vue-hooks"
const Foo = withHooks(h => {
const [width, setWidth] = useState(window.innerWidth)
const handleResize = () => {
setWidth(window.innerWidth)
};
useEffect(() => {
window.addEventListener("resize", handleResize)
return () => {
window.removeEventListener("resize", handleResize)
}
}, [])
return h("div", [
h("div", `window width is: ${width}`)
])
})
В коде useEffect управляет восстановлением ширины при изменении окна.
Возвращаемое значение первого параметра useEffect, если это функция, определяет ее как логику очистки. Логика очистки выполняется до того, как компоненту потребуется повторно выполнить логику useEffect или когда компонент будет уничтожен.
Здесь, в логике useEffect, событие изменения размера добавляется к объекту окна, поэтому, когда компонент уничтожается или необходимо повторно выполнить логику побочного эффекта, событие изменения размера необходимо сначала вывести из системы, чтобы избежать ненужной обработки событий.
Обратите внимание, что здесь значение второго параметра useEffect равно [], что указывает на отсутствие зависимости, побочная логика выполняется только на смонтированной сборке, эта обработка также соответствует этой контекстной сцене.
Детали реализации useEffect:
export function useEffect(rawEffect, deps) {
ensureCurrentInstance()
const id = ++callIndex
if (isMounting) {
// 组件挂载前,重新包装「清理逻辑」与「副作用逻辑」。
const cleanup = () => {
const { current } = cleanup
if (current) {
current()
// 清理逻辑执行完,则重置回 null;
// 如果副作用逻辑二次执行,cleanup.current 会被重新赋值。
cleanup.current = null
}
}
const effect = () => {
const { current } = effect
if (current) {
// rawEffect 的返回值,如果是一个函数的话,则定义为 useEffect副作用 的清理函数。
cleanup.current = current()
// rawEffect 执行完,则重置为 null;
// 如果相关的 deps 发生变化,需要二次执行 rawEffect 时 effect.current 会被重新赋值。
effect.current = null
}
}
effect.current = rawEffect
// 在组件实例上,存储 useEffect 相关辅助成员。
currentInstance._effectStore[id] = {
effect,
cleanup,
deps
}
// 组件实例 mounted 时,执行 useEffect 逻辑。
currentInstance.$on('hook:mounted', effect)
// 组件实例 destroyed 时,执行 useEffect 相关清理逻辑。
currentInstance.$on('hook:destroyed', cleanup)
// 若未指定依赖项或存在明确的依赖项时,组件实例 updated 后,执行 useEffect 逻辑。
// 若指定依赖项为 [], 则 useEffect 只会在 mounted 时执行一次。
if (!deps || deps.length > 0) {
currentInstance.$on('hook:updated', effect)
}
} else {
const record = currentInstance._effectStore[id]
const { effect, cleanup, deps: prevDeps = [] } = record
record.deps = deps
if (!deps || deps.some((d, i) => d !== prevDeps[i])) {
// 若依赖的状态值有变动时,在副作用重新执行前,执行清理逻辑。
cleanup()
// useEffect 执行完毕后,会将 current 的属性置为 null. 这里为 effect.current 重新赋值,
// 是为了在 updated 后执行 rawEffect 逻辑。
effect.current = rawEffect
}
}
}
Можно заметить, что реализация useEffect относительно деликатна и включает три жизненных цикла компонента: монтирование, обновление, уничтожение, а детали выполнения логики побочного эффекта контролируются параметром deps:
- При монтировании выполняется один раз фиксированно.
- Если deps не указан, он будет выполняться после каждого обновления.
- Если deps является пустым массивом, он не будет выполняться после обновления.
- Если deps указывает зависимость, она выполняется один раз при изменении значения соответствующей зависимости.
С помощью параметров мы можем указать 3 вида информации для useEffect:
- rawEffect - содержание логики побочного эффекта.
- Логика очистки — определяется возвращаемым значением rawEffect.
- Зависимость — определяет, когда логика побочного эффекта должна выполняться повторно.
Среди них логика очистки будет выполняться в 2 случаях:
- rawEffect устраняет побочные эффекты предыдущего запуска, прежде чем его нужно будет повторить.
- Когда компонент уничтожен.
useRef
Эквивалентно добавлению локальной переменной к компоненту (некомпонентное состояние).
Подпись метода:
const refContainer = useRef(initialValue)
Детали реализации useRef:
export function useRef(initial) {
ensureCurrentInstance()
const id = ++callIndex
const { _refsStore: refs } = currentInstance
return isMounting ?
(refs[id] = {
current: initial
}) :
refs[id]
}
В коде начальное значение, указанное в useRef, вместе с определением refs самого компонента хранится во внутреннем объекте _refsStore. В функции рендеринга компонента можно в любой момент получить объект ref: refContainer, а в нем получить или изменить текущее свойство.
useData
useData похож на useState, за исключением того, что useData не предоставляет средства обновления.
Детали реализации useData:
export function useData(initial) {
const id = ++callIndex
const state = currentInstance.$data._state
if (isMounting) {
currentInstance.$set(state, id, initial)
}
return state[id]
}
useMounted
Добавьте логику, которая должна выполняться в смонтированном событии.
Use Comounted Детали реализации:
export function useMounted(fn) {
useEffect(fn, [])
}
Это относительно просто.Как упоминалось выше, если параметр deps для useEffect указан как пустой массив, fn не будет выполняться после обновления, то есть он будет выполняться только один раз при монтировании.
useDestroyed
Добавьте логику, которая должна быть выполнена в фазе уничтожения.
детали реализации useDestroyed:
export function useDestroyed(fn) {
useEffect(() => fn, [])
}
Как упоминалось выше, возвращаемое значение первого параметра useEffect, если это функция, будет выполняться как логика очистки в фазе уничтожения.
Здесь, установив значение параметра deps в пустой массив и указав fn в качестве возвращаемого значения логики побочного эффекта useEffect, предотвращается выполнение fn при обновлении компонента, так что fn выполняется только в разрушенная фаза.
useUpdated
Добавьте логику, которую необходимо выполнить после обновления компонента.
useОбновленные детали реализации:
export function useUpdated(fn, deps) {
const isMount = useRef(true) // 通过 useRef 生成一个标识符。
useEffect(() => {
if (isMount.current) {
isMount.current = false // 跳过 mounted.
} else {
return fn()
}
}, deps)
}
Он также реализуется через useEffect, а переменная флага объявляется через useRef, чтобы избежать логики побочных эффектов использования useEffect, выполняемой в Mounted.
useWatch
Добавьте часы к компоненту.
Детали реализации useWatch:
export function useWatch(getter, cb, options) {
ensureCurrentInstance()
if (isMounting) {
currentInstance.$watch(getter, cb, options)
}
}
Реализуется непосредственно через метод $watch экземпляра компонента.
useComputed
Добавьте вычисляемое свойство к компоненту.
Детали реализации useComputed:
export function useComputed(getter) {
ensureCurrentInstance()
const id = ++callIndex
const store = currentInstance._computedStore
if (isMounting) {
store[id] = getter()
currentInstance.$watch(
getter,
val => { store[id] = val },
{ sync: true }
)
}
return store[id]
}
Здесь значение вычисляемого свойства хранится во внутреннем объекте _computedStore. По сути, это также реализуется через $watch экземпляра компонента.
Полный код и пример
Пожалуйста, обратитесь к:POC of vue-hooks
В заключение
Ознакомившись с предысторией хуков, определением хуков и реализацией в React/Vue, можно сделать следующие выводы:
- Хуки, если их широко использовать, коренным образом изменят то, как мы разрабатываем компоненты.
- С помощью хуков мы можем проникнуть во все аспекты жизненного цикла компонентов и собрать состояние и поведение для функциональных чистых компонентов. Степень детализации модуляризации выше, повторное использование кода выше, связность и слабая связанность выше.
- Hooks API — это нейтральная концепция, которую также можно использовать в Vue или других компонентных системах, таких как:React's Hooks API implemented for web components
- Развивая компоненты на пути «чистых компонентов + крючков», мы в основном прощаются с непредсказуемым этим, и код более функционален. В будущем также будет удобно для дальнейшего использования преимуществ функциональных криний, композиции и ленивых вычислений, чтобы написать более лаконичный и надежный код.
- С помощью хуков мы можем организовать модули кода в соответствии с актуальностью бизнес-логики и избавиться от ограничений формата компонента класса.
- Хуки все еще находятся на ранней стадии, но они дают нам хорошую идею для разработки компонентов, которые вы можете испытать в react-16.7.0-alpha.0.
Оригинальная ссылка:Фирменные блюда Овощи сливы Талант/деталь/82,Вы также можете найти апплет "Техническая группа по продуктам Meicai" в WeChat. Он полон галантерейных товаров и обновляется каждую неделю. Если вы хотите изучить технологии, не пропустите его.