Автор: Сяовэнь
Why Hooks API?
Прошло почти два года с тех пор, как Hooks API вошел в поле зрения разработчиков в 2018 году, но все еще есть много студентов, которые не понимают и не понимают Hooks.
-
Зачем переходить с Class API на Hooks API?
-
Использование Hooks API сильно отличается от использования Class API и требует повторного изучения.
-
Большое количество вложенных функций делает «ловушку замыкания» очень распространенной. Нетрудно обнаружить, что в сообществе уже есть много руководств по Hooks API и даже анализу и изучению его исходного кода. Но когда мы на самом деле изучим реализацию Hooks API в исходном коде React, нас еще больше запутает его «волшебная» реализация. Итак, с какой точки зрения мы можем изучить и понять шаблон проектирования Hooks API?
TL;DR
Эта статья содержит следующее:
-
Разница между Hooks API и Class API
-
Функциональное программирование в Hooks API
-
Хуки API и кодовые данные/алгебра
-
React Hooks и Vue Composition API
YES
Эта статья познакомит вас с Hooks API с другой точки зрения и поможет вам лучше понять Hooks API.
NO
Эта статья не учит вас, как использовать Hooks API, и не помогает вам лучше использовать Hooks API.
Ну что, ты все еще хочешь продолжить чтение? Тогда, пожалуйста, не пропускайте ни одного предложения, иначе это серьезно повлияет на впечатления от чтения.
функциональное программирование
На самом деле ядром Hooks API является режим разработки функционального программирования (Functional Programming), и большинство выражений API предоставляются в виде функций. На самом деле функциональное программирование существовало во фреймворке React с самого начала.
View=Render(State)
Реагировать на это понимание неизменной (не модифицируемой) основной модели развития «формул». Выражение jsx реагируют на самом деле функция для преобразования в виртуальное состояние DOM, разработчик определяется JSX State Good - Status> Просмотр промежуточных соотношений преобразования после сборки, просто изменив STATSTATE, изменится через соответствующую функцию React Expression View (рендеринг) Сборка преобразует и все комбинации состояния, фактическая структура страницы, наконец, получена.
В экосистеме разработки React, после многих итераций инструментов управления состоянием, Redux, который также использует функциональное программирование в качестве ядра, стал самым популярным выбором в сообществе разработчиков React. В шаблоне проектирования Redux концепция функционала отражена более полно. Для изменения состояния в магазине Redux требуется действие, чтобы активировать редьюсер. Основная концепция Reducer:
State' = Reducer(State, Action)
interface State {
count: number
}
const state: State = {
count: 0
}
const INCR = (state: State, payload: number = 1): State => {
return {
count: state.count + payload
}
}
const DECR = (state: State, payload: number = 1): State => {
return {
count: state.count - payload
}
}
Управление состоянием Redux на самом деле является «рекордным» результатом, полученным с помощью непрерывной комбинации Reducer+Action.Мы можем увидеть список действий, выполняемых в порядке времени в Redux Devtools, и даже «временной шаттл» в очереди действий. . Однако для студентов, которые не знакомы с функциональным программированием или имеют относительно общие математические основы, стоимость понимания этой модели разработки будет относительно высокой.
Вернемся к самому функциональному программированию Hooks API. Хотя React и Redux имеют высокий уровень поддержки шаблонов функционального программирования, почему компоненты с отслеживанием состояния разрабатываются только в объектно-ориентированной форме? На самом деле модель разработки React Class API основана на модели конечного автомата, представление компонента реагирует изменением this.state в экземпляре компонента. Конечно, в этом режиме нет проблем с разработкой компонентов, но когда некоторая логика в компоненте должна быть абстрагирована и использована повторно, возникает проблема Class API.
Предполагая, что существует логика счетчика, которую необходимо абстрагировать, в Class API есть два абстрактных метода: Mixin и HOC (компонент более высокого порядка).
логическая абстракция
Mixins
Это очень «оригинальный» метод повторного использования логики, который смешивает абстрактную логику с бизнес-компонентами через React.createClass, так что абстрактную логику можно повторно использовать в компонентах. Но этот режим очень ограничительный и подвержен конфликтам.
import React from "react"
import ReactDOM from "react-dom"
import { Row, Col, Button } from "antd"
const counterMixin = {
getInitialState() {
return {
count: 0
}
},
incr(increment = 1) {
this.setState({
count: this.state.count + increment
})
},
decr(decrement = 1) {
this.setState({
count: this.state.count - decrement
})
}
}
// Deprecated since React 15.5.0
const Counter = React.createClass({
mixins: [counterMixin],
render() {
return (
<Row style={{ width: 150, textAlign: "center" }}>
<Col span={24}>
<h3>{this.state.count}</h3>
</Col>
<Col span={12}>
<Button onClick={() => this.incr()}>INCR</Button>
</Col>
<Col span={12}>
<Button onClick={() => this.decr()}>DECR</Button>
</Col>
</Row>
)
}
})
ДЕМО:код sandbox.io/is/counter-i...
HOC
createClass был удален с React 15.5.0, а HOC относительно более гибкий. Основная логика HOC заключается в упаковке бизнес-компонентов и передаче упакованной логики бизнес-компонентам через параметры.
import React, { Component } from "react"
import ReactDOM from "react-dom"
import { Row, Col, Button } from "antd"
function CounterComponent(WrappedComponent) {
return class extends Component {
state = {
count: 0
}
incr(increment = 1) {
this.setState({
count: this.state.count + increment
})
}
decr(decrement = 1) {
this.setState({
count: this.state.count - decrement
})
}
render() {
const countProps = {
count: this.state.count,
incr: (increment) => this.incr(increment),
decr: (decrement) => this.decr(decrement)
}
return <WrappedComponent {...this.props} {...countProps} />
}
}
}
const Counter = CounterComponent((props) => {
const { count, incr, decr } = props
return (
<Row style={{ width: 150, textAlign: "center" }}>
<Col span={24}>
<h3>{count}</h3>
</Col>
<Col span={12}>
<Button onClick={() => incr && incr()}>INCR</Button>
</Col>
<Col span={12}>
<Button onClick={() => decr && decr()}>DECR</Button>
</Col>
</Row>
)
})
ДЕМО:код sandbox.io/is/counter-i...
Этот метод кажется простым, но если вам нужно добавить состояние в бизнес-компонент, это более хлопотно. Если вам нужно передать более сложную логику компонента, вам нужно использовать такие методы, как соединение, чтобы достичь этого.
Так как же это требование должно быть реализовано в Hooks API?
Hooks API
Hooks API React позволяет фактически действовать как КОМПОНЕНТ БЕЗ СОСТОЯНИЯ, также имеет возможность STATEFUL, а сами хуки также реализуются функцией.
import React, { useState } from "react"
import ReactDOM from "react-dom"
import { Row, Col, Button } from "antd"
function useCount(initCount = 0) {
const [count, setCount] = useState(initCount)
const incr = () => setCount(count + 1)
const decr = () => setCount(count - 1)
return {
count,
incr,
decr
}
}
function Counter() {
const { count, incr, decr } = useCount()
return (
<Row style={{ width: 150, textAlign: "center" }}>
<Col span={24}>
<h3>{count}</h3>
</Col>
<Col span={12}>
<Button onClick={() => incr()}>INCR</Button>
</Col>
<Col span={12}>
<Button onClick={() => decr()}>DECR</Button>
</Col>
</Row>
)
}
ДЕМО:код sandbox.io/is/counter-i...
Хм, выглядит намного проще. Но когда мы читаем измененное состояние, например, используя Class API + Async/Await, происходят странные вещи. Например, нам нужно распечатать новый счетчик после выполнения incr.В Class API, если мы используем await для асинхронного выполнения при выполнении this.setState, мы можем получить правильное новое значение в коде позади. В Hooks API даже использование Async/Await не может получить правильное значение.
import React, { useState } from "react"
import ReactDOM from "react-dom"
import { Row, Col, Button, Collapse, Input } from "antd"
const { TextArea } = Input
const { Panel } = Collapse
class CounterA extends React.Component {
state = {
count: 0
}
async incr(increment = 1) {
await this.setState({
count: this.state.count + increment
})
}
async incrAndLog(increment = 1) {
await this.incr(increment)
this.props.log("Class API", this.state.count) // Here will log the new value
}
decr(decrement = 1) {
this.setState({
count: this.state.count - decrement
})
}
render() {
return (
<Row style={{ width: 300, textAlign: "center" }}>
<Col span={24}>
<h3>{this.state.count}</h3>
</Col>
<Col span={8}>
<Button onClick={() => this.incr()}>INCR</Button>
</Col>
<Col span={8}>
<Button onClick={() => this.incrAndLog()}>INCR&LOG</Button>
</Col>
<Col span={8}>
<Button onClick={() => this.decr()}>DECR</Button>
</Col>
</Row>
)
}
}
function useCount(initCount = 0) {
const [count, setCount] = useState(initCount)
const incr = async () => await setCount(count + 1)
const decr = async () => await setCount(count - 1)
return {
count,
incr,
decr
}
}
function CounterB({ log }) {
const { count, incr, decr } = useCount()
const incrAndLog = async (increment = 1) => {
await incr(increment)
log("Hooks API", count) // Here will log the previous value, WHY?
}
return (
<Row style={{ width: 300, textAlign: "center" }}>
<Col span={24}>
<h3>{count}</h3>
</Col>
<Col span={8}>
<Button onClick={() => incr()}>INCR</Button>
</Col>
<Col span={8}>
<Button onClick={() => incrAndLog()}>INCR&LOG</Button>
</Col>
<Col span={8}>
<Button onClick={() => decr()}>DECR</Button>
</Col>
</Row>
)
}
function App() {
const [logText, setLogText] = useState("[LOGGING HERE]")
const log = (domain, msg) => {
setLogText(logText + "\n" + `[${domain}] ${msg}`)
}
return (
<Row gutter={15}>
<Col span={12}>
<h4>DEMO</h4>
<Collapse defaultActiveKey={[1]}>
<Panel header="Counter with Class API" key={1}>
<CounterA log={log} />
</Panel>
<Panel header="Counter with Hooks API" key={2}>
<CounterB log={log} />
</Panel>
</Collapse>
</Col>
<Col span={12}>
<h4>Log</h4>
<TextArea rows={8} value={logText} />
</Col>
</Row>
)
}
ДЕМО:код sandbox.io/yes/weird-…
Не кажется ли вам, что, хотя Hooks API делает код более чистым при использовании, странные умственные затраты сильно увеличиваются? Да, я полностью согласен с этим, Hooks API вводит много абстракции и неясности, что приводит к крутой кривой обучения на начальном этапе.
Но не бойтесь, позвольте мне рассказать вам, как использовать TypeScript, чтобы понять и изучить функциональное программирование и Hooks API.
Начните с функционального программирования на основе вывода типа TS
базовый тип
Во-первых, давайте узнаем два основных типа функций:
-
F\text{} \rightarrow T, тип параметра функции F — T, тип значения, возвращаемого функцией, — также T;
-
F\text{} \rightarrow U, тип параметра функции F — T, а тип значения, возвращаемого функцией, — U;
Простое объяснение состоит в том, что первое состоит в том, что выходные параметры и входные параметры функции имеют один и тот же тип, а второе состоит в том, что после того, как входные параметры функции выполняются функцией, типы выходных параметров несовместимы. с входными параметрами, что можно понимать как Сопоставление было выполнено.
тип вычет
Теперь, когда у нас есть эти два основных типа, мы можем начать их трансформировать. Но перед этим давайте посмотрим набольшой промышленныйдве фразы:
Данные, которых я не знаю, это все параметры;
Я не знаю поведения, это все параметры;
Эти два предложения в основном охватывают более 90% сценариев функционального программирования в контексте веб-разработки, поэтому мы можем понять значение этих двух предложений из следующей серии вариантов функций.
-
F\text{<}T\text{>} \rightarrow T
- F\text{}{>} \rightarrow F\text{}, функция F\text{} Как тип входного параметра другой функции F, он также является типом ее выходного параметра, см. пример: useCallback
-
F\text{<}T\text{>} \rightarrow U
-
F\text{}{>} \rightarrow T, в отличие от приведенного выше, возвращаемое значение является параметром F\text{} T, ссылочный пример: useMemo
-
F\text{}{>} \rightarrow (F\text{}, U), передать функцию F\text{}, возвращает не только эту функцию, но и некоторые другие состояния U, см. пример:useRequest
-
F\text{} \rightarrow (T, F\text{}), передать тип T, в дополнение к возврату самого T, он также возвращает функцию F\text {}, ссылочный пример: useState
-
...
-
Я не знаю данных, это все параметры
Это предложение легко понять. Ссылаясь на Lodash, самый популярный «швейцарский армейский нож» в сообществе разработчиков JavaScript, многие методы обработки данных можно рассматривать как F\text{} \rightarrow U или F \text{} \rightarrow U\text{>} Воплощение \rightarrow U.
В функции, хотя я и не знаю, какие данные хочу обработать, даже неизвестен конкретный тип, определена логика, которую нужно выполнить.
ДЕМО:код sandbox.io / да / дребезжание - большой...
Я не знаю поведения, это все параметры
В веб-разработке нам часто нужно контролировать некоторые пользовательские поведения в бизнес-логике, такие как:
-
Для отправки Ajax-запроса вам необходимо знать следующую информацию:useRequest
-
Ожидает ли запрос возврата: статус загрузки
-
Был ли запрос успешно возвращен: статус успеха
-
Должна ли запрос вручную спусковой крючок: условие триггера
-
Если запрос запускается автоматически, каковы условия запуска: Зависимое обновление
-
Нужно ли кешировать запрос: состояние кеша, статус кеша
-
-
Существует ряд поведений, связанных с асинхронными операциями.Чтобы уменьшить параллелизм системы, они должны автоматически ставиться в очередь и выполняться последовательно:runTrack
-
Ожидается, что в том месте, где определено поведение, оно будет невосприимчивым.
-
Исполнителю не нужно знать о существовании очереди
-
-
...
Мы отправляем запрос сцены AJAX в качестве примера к первому, мы надеемся получить инструмент, способный проводить реальный бизнес-пакет, и участие в использовании в качестве ссылки с поведением после того, как пакет одинаково.
const { run: fetchPosts, loading } = wrapRequest((authorId: number) => fetchPostsService(authorId))
fetchPosts(123)
// ...
const statusText = loading ? "Loading..." : "Done"
На самом деле у нас также есть очень знакомый сценарий «поведение, которого я не знаю», который представляет собой процесс компоновки компонентов (Compose) и рендеринга (Render).Далее нам нужно ввести еще одно понятие — codata.
На самом деле не такие уж эзотерические PLT/математические понятия — codata
Не бойтесь, в codata на самом деле нет ничего нового. Когда мы учились решать уравнения в начальной школе по математике, мы уже начали соприкасаться с понятием под названием алгебра (алгебра). Какой? Алгебра слишком абстрактна, чтобы ее можно было сравнивать с программированием? Неважно, давайте пошагово.
S=πr^2
Вспомните формулу вычисления площади круга: S=\pi r^2, где:
-
S — площадь круга, это значение, которое нужно найти по этой формуле;
-
\pi равно pi. Как обычное иррациональное число, оно будет иметь разную точность приближения в разных сценариях. Например, мы обычно используем 3,14 в качестве приближения, которое легко вычислить вручную, когда мы учимся. В сцене это может быть необходимо использовать N знаков после запятой;
- Однако на самом деле мы должны думать о \pi как о результате алгоритма вычисления числа пи в различных приближениях.
-
r — радиус окружности, и для самой этой формулы r — тоже неизвестное.
В такой простой формуле, включая саму формулу, уже три алгебры. В математике мы можем думать о неизвестных и алгоритмах как об алгебрах Алгебры не имеют конкретных значений, но мы знаем, как их использовать.
Немного абстрактно? Это не имеет значения, давайте возьмем в качестве примера контекст веб-разработки, предполагая, что необходимо реализовать следующие PRD:
В этот PRD включены следующие элементы:
-
Ползунок, который может изменить радиус r круга
-
Переключатель для изменения приближения, используемого pi\pi
-
Круг, размер которого соответствует выбранному радиусу
-
Строка, показывающая площадь круга
Чтобы использовать настоящие хуки React для реализации элементов в PRD, вам необходимо определить следующие вещи (псевдокод):
import { useState, useMemo } from 'react'
function App() {
const [r, setR] = useState(10)
const [PI, setPI] = useState(3.14)
const S = useMemo(() => PI * Math.pow(r, 2), [PI, r])
const circleCSS = useMemo(() => ({
width: r * 2,
height: r * 2,
}), [r])
}
В этом коде определены четыре объекта, которые соответствуют четырем элементам в вышеуказанных требованиях.Эти четыре элемента уже могут соответствовать разработке всех частей только для чтения в PRD. Но в PRD можно изменить два самых важных элемента радиуса окружности r и pi\pi, поэтому до фактического расчета фактические значения этих четырех элементов неизвестны. Только когда компонент фактически визуализируется, среда React фактически считывает текущее выбранное пользователем чувствительное ко времени значение из памяти для вычисления.
Compose & Visit
Хм, ты чувствуешь себя немного? Мы можем разделить процесс использования кодовых данных на две части: составить и посетить. Предположим, мы также выражаем эту функциональную составляющую на математическом языке, она может быть в виде:
Render = F\text{<}\pi,r,S,\text{circleCSS}\text{>}
Где S и \text{circleCSS} можно переписать как F\text{} и F\text{}, поэтому функцию рендеринга можно записать как следующую форму:
Render = F\text{<}\pi,r,F\text{<}\pi,r\text{>},F\text{<}r\text{>}\text{>}
Разве это не кажется проще и понятнее? Эта функция рендеринга на самом деле является бинарной функцией радиуса r и пи\пи, и в области веб-разработки эта функция может принимать частные производные от этих двух элементов соответственно. Забыли, что такое частичное руководство? Это не имеет значения, простое понимание состоит в том, что любые изменения в этих двух элементах приведут к тому, что изменения всего компонента в целом будут спекулятивными. Вы что-нибудь помните? Да, это тоже философия Redux.
DEMO: code sandbox.IO/yes/юань рис ах...
Мы создаем функцию рендеринга Render = F\text{},F\text{}\text{ > } Этот процесс на самом деле представляет собой процесс комбинирования, в котором каждому неизвестному элементу на самом деле не нужно использовать свое фактическое значение, а используется только сама его концепция. Только когда функция фактически наблюдается, то есть визуализируется, фактическое значение внутри будет наблюдаться и использоваться для вычисления значения каждого уровня функции и, наконец, получения фактического результата функции рендеринга.
Когда вы повторно используете логику путем написания пользовательских хуков, вы фактически используете эти пользовательские хуки в качестве параметра в бизнес-компоненте, который представляет собой процесс композиции.
Преимущество этого заключается в том, что сам компонент, естественно, более удобен для ленивых вычислений (Lazy Compute), и у него больше возможностей для настройки производительности. В React Hooks также предусмотрены useMemo и useCallback для сокращения ненужных вычислений в процессе наблюдения и дальнейшей оптимизации производительности.
Однако реальность жестока, большинство компонентов невозможно идеально переписать в производные функции, потому что нам часто нужно использовать некоторые побочные эффекты для достижения наших более сложных потребностей, таких как метод useEffect, также предусмотренный в React Hooks.
Вернемся снова к определению самого типа функции.Функция рендеринга на самом деле является вариантом F\text{} \rightarrow U, поэтому тип T параметра функции, представляющего рендеринг, должен быть в процесс совмещения с целью получения некоего U, то есть результата наблюдения - интерфейса рендеринга. С этой точки зрения это также может объяснить, почему Hooks API не позволяет использовать Hooks API ни в логических ветвях, ни в подфункциях, потому что оба случая могут привести к нестабильности параметра T в процессе объединения. В этом случае результатом является то, что тип результата U также является неопределенным и не соответствует ожиданиям.
Следует отметить, что упомянутое здесь Т относится не конкретно к определенному параметру, а ко всей комбинации параметров. Изменения количества и порядка комбинации параметров означают, что этот Т изменился, или это уже не этот Т.
Конечно, у codata больше возможностей, чем у алгебры, но мы не будем их здесь раскрывать, а можно написать отдельную статью, чтобы подробно рассказать об этом знакомом и незнакомом понятии.
React Hooks и Vue Composition API
Мы знаем, что после того, как Hooks API был впервые выпущен в React, другой популярный веб-фреймворк, Vue, также анонсировал свою версию 3.0. Самая большая разница между Vue 3.0 и 2.x заключается в том, что версия 3.0 также представила концепцию Hooks API и имеет другое название —Composition API.
По сравнению с функциональными компонентами React Hooks, Vue Composition API по-прежнему сохраняет уникальное традиционное преимущество Vue — Single File Component. Vue по-прежнему сохраняет более примитивный и более управляемый механизм шаблонов, чем React, использующий JSX в качестве выражения уровня представления.
import Vue from "vue"
import VueCompositionAPI, { ref } from "@vue/composition-api"
import Antd from "ant-design-vue"
Vue.use(VueCompositionAPI)
Vue.use(Antd)
const template = `
<a-row id="app">
<a-col :span="24">
<h3>{{count}}</h3>
</a-col>
<a-col :span="12">
<a-button @click="() => incr()">INCR</a-button>
</a-col>
<a-col :span="12">
<a-button @click="() => decr()">DECR</a-button>
</a-col>
</a-row>
`
new Vue({
name: "App",
template,
setup(props) {
const count = ref(0)
const incr = (increment = 1) => (count.value += increment)
const decr = (decrement = 1) => (count.value -= decrement)
return {
count,
incr,
decr
}
}
}).$mount("#app")
ДЕМО:код sandbox.IO/yes/v UE-compo…
Нетрудно обнаружить, что процесс композиции и наблюдения также одинаков.В Vue Composition API соответствующий метод настройки и отрисовка шаблона разделены на две части (конечно, это может быть и функция рендеринга на основе JSX ), который сравнивают с React Hooks. Говорят, что он ближе к концепции кодовых данных. А с точки зрения принципа реализации, процесс Compose API Vue Composition выполняется только один раз, по сравнению с React Hooks, которые будут повторно выполнять композицию каждый раз при изменении любого параметра, что более управляемо и проще настроить на производительность.
Кроме того, Vue Composition API предназначен для разработчиков, которые изначально использовали Vue 2.x API. }T\ text{>}) сжата в F\text{} \rightarrow Ref\text{}, что позволяет переварить большую часть сложных умственных затрат внутри структуры. , значительно снижая порог входа для Hooks API.
Play together?
Настолько, что теперь есть проекты, которые объединяют дваantfu/reactivue, это забавная идея.
import React from "react"
import { defineComponent, ref } from "reactivue"
import { Row, Col, Button } from "antd"
const Counter = defineComponent(
// Setup
() => {
const count = ref(0)
const incr = (increment = 1) => (count.value += increment)
const decr = (decrement = 1) => (count.value -= decrement)
return {
count,
incr,
decr
}
},
// Render
({ count, incr, decr }: any) => (
<Row style={{ width: 150, textAlign: "center" }} className="App">
<Col span={24}>
<h3>{count}</h3>
</Col>
<Col span={12}>
<Button onClick={() => incr()}>INCR</Button>
</Col>
<Col span={12}>
<Button onClick={() => decr()}>DECR</Button>
</Col>
</Row>
)
)
ДЕМО:код sandbox.io/is/blue-fast…
наконец
Спасибо, что так долго читали статью без руководств.Если эта статья может принести вам больше новых идей, то переосмысление Hooks API и разработка приложений React — величайшее достижение этой статьи.
Когда мы изучаем новую технологию, мы часто сначала думаем, стоит ли читать ее исходный код для «углубленного изучения», но на самом деле иногда исходный код гораздо менее важен, чем идея дизайна, и Hooks API является очень типичным примером. пример. Реализация хуков на самом деле очень сложна, поэтому, если вы изучаете хуки только с точки зрения исходного кода, вы не сможете пройти первоначальный путь изучения хуков в течение длительного времени и постепенно потеряете к ним интерес.
У меня скромное мнение, и я надеюсь посоветовать.