Введение и обзор хуков?
Хуки — это особый тип функций в функциональных компонентах React (обычно начинающийся с «use», например «useState»), позволяющий разработчикам по-прежнему использовать состояние и жизненные циклы в функциональных компонентах, а также повторно использовать бизнес-логику с пользовательскими хуками.
Зачем внедрять хуки и какие проблемы решать
Общие проблемы, с которыми сталкиваются текущие реакции:
- Сложно повторно использовать логику (используйте только HOC или реквизиты рендеринга), что приведет к глубокой иерархии дерева компонентов.
- Большие компоненты трудно разбивать и рефакторить, а также трудно тестировать.
- Компоненты класса сложны для понимания, как методы требуют
bind
,this
Пункт не ясен - Бизнес-логика разбросана по различным методам компонента, что приводит к дублированию логики или связанной с ней логики.
Хуки позволяют нам лучше повторно использовать логику кода. Функциональные компоненты могут использоваться для логического повторного использования, но функциональные компоненты не имеют состояния и могут отображаться только как [чистые компоненты] и не могут обрабатывать локальное состояние. Хуки позволяют функциональным компонентам иметь локальное состояние и могут обрабатывать логику состояния.
Типы крючков
- State hooks(использовать состояние в функциональном компоненте)
- Effect hooks(использовать жизненный цикл и побочные эффекты в функциональном компоненте)
- Custom hooks(Пользовательские хуки используются для повторного использования логики компонентов, решая проблему, описанную в первой мотивации выше)
State Hooks
import { useState } from 'react';
function Example() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Крючки вернут кортеж, структура[value, setValue]
.
Эти два возвращаемых значения соответствуют предыдущим в реакции
- this.state
- this.setState
Мы также можем использовать несколько состояний в функции одновременно.
function ExampleWithManyStates() {
// Declare multiple state variables!
const [age, setAge] = useState(42);
const [fruit, setFruit] = useState('banana');
const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);
// ...
}
Перед обновлением значения состояния передайтеthis.setState({ fruit: 'orange' })
, предыдущее состояние и обновленное состояние будут объединены.
С помощью хуков состояние будет разбито на значения, и после обновления новое значение будет заменено напрямую, а состояние не будет объединено. Структура [state, setState] также делает логику обновления значения более понятной.
Общие хуки, предоставляемые React по умолчанию
- useState()
- useContext()
- useReducer()
useContext()
Сотрудничать
React.createContext({})
использование, общее состояние между компонентами
Пример:
const AppContext = React.createContext({});
<AppContext.Provider value={{
username: 'superawesome'
}}>
<div className="App">
<Navbar/>
<Messages/>
</div>
</AppContext.Provider>
Затем вы можете использовать AppContext непосредственно в компоненте Navbar.
const Navbar = () => {
const { username } = useContext(AppContext);
return (
<div className="navbar">
<p>AwesomeSite</p>
<p>{username}</p>
</div>
);
}
useReducer()
Он используется для простой замены избыточности для управления состоянием, но не может обеспечить сложные сценарии, такие как промежуточное ПО и путешествия во времени.
Пример:
const initialState = {count: 0};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</>
);
}
Effect Hooks
EffectHooks позволяют нам использовать методы жизненного цикла внутри функциональных компонентов, где мы можем обновлять DOM, извлекать данные и т. д. с поведением «побочных эффектов». Хук эффекта будет выполняться после каждого рендера компонента, а функция возврата будет выполняться при выгрузке компонента.Если вы хотите, чтобы хук эффекта выполнялся только при первой загрузке компонента, вы можете передать в пустой массив в качестве второго параметра, или вы можете использовать массив Укажите зависимости в , effectHooks будет выполняться только при изменении зависимостей.
import { useState, useEffect } from 'react';
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
}
Custom Hooks
Пользовательский хук — это функция javascript, которая начинается с «use» и может вызывать другие хуки для инкапсуляции логики и повторного использования кода. Например:
import React, { useState, useEffect } from 'react';
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
useEffect(() => {
ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
};
});
return isOnline;
}
Можно использовать другие функциональные компоненты:
function FriendStatus(props) {
const isOnline = useFriendStatus(props.friend.id);
if (isOnline === null) {
return 'Loading...';
}
return isOnline ? 'Online' : 'Offline';
}
*********************************
function FriendListItem(props) {
const isOnline = useFriendStatus(props.friend.id);
return (
<li style={{ color: isOnline ? 'green' : 'black' }}>
{props.friend.name}
</li>
);
}
Хуки должны использоваться на верхнем уровне функций и не могут использоваться в условиях, циклах или вложенности. Хуки постепенно и полностью заменят компоненты класса и в настоящее время не могут поддерживать функции жизненных циклов getSnapshotBeforeUpdate и componentDidCatch.
Принцип реализации хуков
Прежде всего, нам нужно разобраться с механизмом обновления данных и рендеринга представления. предварительно позвонивsetState
Чтобы изменить данные, страница перерисовывается, давайте сначала посмотримsetState
как это работает.
реагирующая инфраструктура
Инфраструктура React разделена на три части: базовый пакет react, react-reconciler, модуль рендеринга рендеринга.
базовый модуль реакции: React базовый API и классы компонентов, компоненты определяют методы render , setState и методы обратного вызова, связанные с жизненным циклом. Связанные API следующие:
const React = {
Children: {},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
Fragment: REACT_FRAGMENT_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
unstable_AsyncMode: REACT_ASYNC_MODE_TYPE,
unstable_Profiler: REACT_PROFILER_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
};
модуль рендеринга рендерера: Используйте разные методы рендеринга для разных сред хоста, такие как react-dom, react-webgl, react-native, react-art, полагайтесь на модуль react-reconciler, внедряйте соответствующие методы рендеринга в reconciler, а связанные с ними в react-dom API выглядит следующим образом:
const ReactDOM: Object = {
createPortal,
findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {},
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {},
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {},
unstable_renderSubtreeIntoContainer() {},
unmountComponentAtNode(container: DOMContainer) {},
unstable_batchedUpdates: DOMRenderer.batchedUpdates,
unstable_deferredUpdates: DOMRenderer.deferredUpdates,
unstable_interactiveUpdates: DOMRenderer.interactiveUpdates,
flushSync: DOMRenderer.flushSync,
unstable_flushControlled: DOMRenderer.flushControlled,
}
основной модуль реактивного согласователя: Отвечает за алгоритм планирования и сравнение дерева волокон, подключение базового пакета реакции и модуля рендеринга, внедрение метода setState в экземпляр компонента, выполнение метода рендеринга в компоненте реакции на этапе сравнения, выполнение обратного вызова жизненного цикла в компоненте реакции. на этапе патча и вызывая инъекцию в рендерере. Соответствующий метод рендеринга реальной структуры представления.
Как работает setState
setState
Он определен в React.Component, но пакет React определяет только API и не реализует логику. Похожие такжеcreateContext()
и т.д. Большинство функций реализовано в «рендерере». react-dom, react-dom/server, react-native, react-test-renderer, react-art — все это распространенные рендереры. Поэтому, когда мы используем новые функции реакции, необходимо обновить как реакцию, так и реакцию.
setState
Определить средство обновления в React.Component
Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Средство обновления будет реализовано само по себе в конкретном рендерере:
// React DOM 内部
var classComponentUpdater = {
isMounted: isMounted,
enqueueSetState: function (inst, payload, callback) {
var fiber = get(inst);
var currentTime = requestCurrentTime();
var expirationTime = computeExpirationForFiber(currentTime, fiber);
var update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
{
warnOnInvalidCallback$1(callback, 'setState');
}
update.callback = callback;
}
flushPassiveEffects();
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
enqueueReplaceState: function (inst, payload, callback) {
//注释了
},
enqueueForceUpdate: function (inst, callback) {
//注释了
}
};
HooksТот же дизайн также используется с использованием объекта «диспетчер» вместо «обновителя». мы называемuseState()
пересылаются текущему диспетчеру.
Используются как поле средства обновления, так и объект диспетчера.внедрение зависимостив виде общих принципов программирования. В обоих случаях средство визуализации «внедряет» реализацию таких функций, как setState, в общий пакет React, чтобы сделать компонент более декларативным.
Как работает useState
Как useState позволяет функциональным компонентам без сохранения состояния сохранять состояние, обновлять представления и в чем разница между обновлением this.setState?
В React есть базовый объект ReactElement, который создается с помощью React.createElement().
React.createElement(
type,
[props],
[...children]
)
//举个例子
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
//完全等价于
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
const element = {
$$typeof: REACT_ELEMENT_TYPE, // 是否是普通Element_Type
// Built-in properties that belong on the element
type: type, // 我们的组件,比如`class MyComponent`
key: key,
ref: ref,
props: props,
children: children,
// Record the component responsible for creating this element.
_owner: owner,
};
Это узел vdom.До React16 React генерировал настоящую структуру DOM на основе этого узла vdom. После React16 была официально введена структура Fiber, и базовая структура реакции стала более сложной. React будет соответствовать узлу vdom как узлу Fiber, структура узла Fiber:
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null; // 就是ReactElement的`$$typeof`
this.type = null; // 就是ReactElement的type
this.stateNode = null;
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.memoizedState = null;
this.updateQueue = null;
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.firstContextDependency = null;
// ...others
}
один из нихthis.updateQueue
Очередь обновлений, используемая для хранения setState,this.memoizedState
Для хранения состояния в компоненте компонент класса используется для хранения объекта состояния, а хуки используются для хранения объекта хука.
//类组件中更新state的update对象
var update = {
expirationTime: expirationTime,
tag: UpdateState,
payload: null,
callback: null,
next: null,
nextEffect: null
};
//函数组件中的Hook对象
{
baseState,
next,
baseUpdate,
queue,
memoizedState
};
//类组件中的updateQueue的结构
var queue = {
baseState: baseState,
firstUpdate: null,
lastUpdate: null,
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null
};
//每新增一个update就加入到队列中
function appendUpdateToQueue(queue, update) {
// Append the update to the end of the list.
if (queue.lastUpdate === null) {
// Queue is empty
queue.firstUpdate = queue.lastUpdate = update;
} else {
queue.lastUpdate.next = update;
queue.lastUpdate = update;
}
}
Механизм обновления memoizedState
Обновление хуков делится на два этапа, операция монтирования выполняется во время инициализации, а операция обновления выполняется во время обновления. Объекты HooksDispatcherOnMountInDEV и HooksDispatcherOnUpdateInDEV используются для хранения всех функций обновления хуков.
HooksDispatcherOnMountInDEV = {
readContext: function (context, observedBits) {
},
useCallback: function (callback, deps) {
},
useContext: function (context, observedBits) {
},
useEffect: function (create, deps) {
},
useImperativeHandle: function (ref, create, deps) {
},
useLayoutEffect: function (create, deps) {
},
useMemo: function (create, deps) {
},
useReducer: function (reducer, initialArg, init) {
},
useRef: function (initialValue) {
},
useState: function (initialState) {
var hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
var queue = hook.queue = {
last: null,
dispatch: null,
eagerReducer: basicStateReducer,
eagerState: initialState
};
var dispatch = queue.dispatch = dispatchAction.bind(null,
// Flow doesn't know this is non-null, but we do.
currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
},
useDebugValue: function (value, formatterFn) {
}
};
//其中的dispatch即为我们调用的‘setState’函数,核心代码为:
function dispatchAction(fiber, queue, action) {
//注释了*******
var update = {
expirationTime: renderExpirationTime,
action: action,
eagerReducer: null,
eagerState: null,
next: null
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
renderPhaseUpdates.set(queue, update);
}
HooksDispatcherOnUpdateInDEV = {
//注释了**********
useState: function (initialState) {
currentHookNameInDev = 'useState';
var prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
try {
return updateState(initialState);
} finally {
ReactCurrentDispatcher$1.current = prevDispatcher;
}
},
};
function updateState(initialState) {
return updateReducer(basicStateReducer, initialState);
}
function updateReducer(reducer, initialArg, init) {
// 注释了**********
var firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate !== undefined) {
renderPhaseUpdates.delete(queue);
var newState = hook.memoizedState;
var update = firstRenderPhaseUpdate;
do {
// Process this render phase update. We don't have to check the
// priority because it will always be the same as the current
// render's.
var _action = update.action;
newState = reducer(newState, _action);
update = update.next;
} while (update !== null);
}
Действие в объекте обновления заключается в использовании параметра setState. Обновление будет добавлено в очередь обновлений. После того, как все «обновления» будут собраны, будет запущено обновление реакции. При обновлении выполните useState в функциональном компоненте, затем получите объект Hook, выньте объект очереди, обновите его по очереди, получите новое состояние и сохраните его в memoizedState, а затем вернитесь, чтобы обновить представление.
вmemoizedState
используется для записи этогоuseState
должен вернуть результат, аnext
указывая на следующий разuseState
Соответствующий объект `Hook.
пример:
function FunctionalComponent () {
const [state1, setState1] = useState(1)
const [state2, setState2] = useState(2)
const [state3, setState3] = useState(3)
}
Порядок выполнения такой:
hook1 => Fiber.memoizedState
state1 === hoo1.memoizedState
hook1.next => hook2
state2 === hook2.memoizedState
hook2.next => hook3
state3 === hook2.memoizedState
next зависит от значения предыдущего состояния.Если useState не выполняется, соответствующее отношение будет испорчено. Поэтому в React оговаривается, что при использовании хуков они должны использоваться в корневой области видимости и не могут использоваться в условных операторах и циклах.
Имитационные крючки
Разберем характеристики хуков:
- Вызовите useState() и верните Tuple([value,setValue])
- useState(), который можно использовать только в области верхнего уровня, зависит от порядка, в котором он был создан.
- Может использоваться только в функциональных компонентах, а не в компонентах класса.
Реализация может быть смоделирована с использованием структуры массива:
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
export function useState(initVal) {
if (firstRun) {
state.push(initVal);
setters.push(createSetter(cursor));
firstRun = false;
}
const setter = setters[cursor];
const value = state[cursor];
cursor++;
return [value, setter];
}
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
}
console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']
// click the 'Fred' button
console.log(state); // After-click: ['Fred', 'Yardley']