Как быстро начать работу с VUE3.0: войдите и изучите
В проектах React существует множество сценариев, в которых требуется Ref
. Например, используйте атрибут ref
для получения узла DOM и экземпляра объекта ClassComponent; используйте useRef
Hook для создания объекта Ref, чтобы решить проблему невозможности setInterval
получить последнее состояние; вы также можете вызвать React.createRef
метод для ручного создания объекта Ref
.
Несмотря на то, что Ref
очень прост в использовании, в реальных проектах по-прежнему неизбежно возникают проблемы. В этой статье будут рассмотрены различные проблемы, связанные с Ref
с точки зрения исходного кода и разъяснено, что делается за API, связанными с ref
. Прочитав эту статью, вы, возможно, получите более глубокое понимание Ref
.
Прежде всего, ref
— это аббревиатура reference
, которая является ссылкой. В файле объявления типа react
вы можете найти несколько типов, связанных с Ref, и они перечислены здесь.
RefObject<T> {текущий только для чтения: T | интерфейс MutableRefObject<T> { current: T; }
При использовании useRef
Hook возвращается RefObject/MutableRefObejct. Оба типа определяют структуру объекта { current: T }
. Разница в том, что текущее свойство RefObject
доступно только для чтения. , Typescript предупредит ⚠️, если refObject.current
изменен.
const ref = useRef<строка>(ноль) ref.current = '' // Ошибка
TS: невозможно присвоить «current», поскольку это свойство доступно только для чтения.
Посмотрите на определение метода useRef
. Здесь используется перегрузка функции . Если входящий универсальный параметр T
не содержит null
, возвращается RefObject<T>
. Если он содержит null
, возвращается MutableRefObject<T>
.
функция useRef<T>(initialValue: T): MutableRefObject<T>; function useRef<T>(initialValue: T | null): RefObject<T>;
Итак, если вы хотите, чтобы текущее свойство созданного объекта ссылки было модифицируемым, вам нужно добавить | null
.
const ref = useRef<строка | ноль>(ноль) ref.current = '' // Хорошо,
при вызове метода React.createRef()
также возвращается RefObject
.
createRef
createRef(): RefObject { const refObject = { ток: ноль, }; если (__DEV__) { Object.seal(refObject); } вернуть refObject; }
RefObject/MutableRefObject
был добавлен в версии 16.3
. Если вы используете более ранние версии, вам необходимо использовать Ref Callback
.
Использование Ref Callback
предназначено для передачи функции обратного вызова. При ответном вызове соответствующий экземпляр будет передан обратно, и его можно сохранить для вызова. Тип этой функции обратного вызова — RefCallback
.
type RefCallback<T> = (экземпляр: T | null) => void
Пример использования RefCallback
:
импортируйте React из 'react';
класс экспорта CustomTextInput расширяет React.Component { textInput: HTMLInputElement | ноль = ноль; saveInputRef = (элемент: HTMLInputElement | null) => { this.textInput = элемент; } оказывать() { возвращаться ( <input type="text" ref={this.saveInputRef} /> ); } }
В объявлении типа также присутствуют типы Ref/LegacyRef, которые обычно используются для ссылки на тип Ref. LegacyRef
— совместимая версия. В предыдущей старой версии ref
также мог быть строкой.
тип Ref<T> = RefCallback<T> | RefObject<T> null; type LegacyRef<T> = string | Ref<T>;
Только понимая типы, связанные с Ref, вы сможете более комфортно писать на машинописном языке.
ПередачаПри использовании ref
в компоненте JSX мы устанавливаем Ref
для атрибута ref
. Мы все знаем, что синтаксис jsx
будет скомпилирован в форму createElement
с помощью таких инструментов, как Babel.
//jsx <App ref={ref} id="my-app" ></App> // скомпилировано в React.createElement(App, { ссылка: ссылка, идентификатор: "мое приложение" });
Кажется, что ref
ничем не отличается от других реквизитов, но если вы попытаетесь напечатать props.ref внутри компонента, он будет undefined
. И консоль среды dev
выдаст подсказки.
Попытка доступа к нему приведет к возврату значения
undefined
. Если вам нужно получить доступ к тому же значению в дочернем компоненте, вам следует передать его как другое свойство.
Что React делает с ref? Как вы можете видеть в исходном коде ReactElement, ref
— RESERVED_PROPS
. key
также имеют такую обработку. Они будут специально обработаны, извлечены из реквизита и переданы в Element
.
const RESERVED_PROPS = { ключ: правда, ссылка: правда, __self: правда, __источник: правда, };
Итак, ref
— это “props“
, который будет обрабатываться особым образом.
До версии 16.8.0
функциональный компонент не имел состояния и отображался только на основе входящих реквизитов. С помощью Hook вы можете не только иметь внутреннее состояние, но и предоставлять методы для внешних вызовов (требующих forwardRef
и useImperativeHandle
).
Если вы используете ref
непосредственно для Function Component
, консоль в среде разработки предупредит вас, что вам нужно обернуть его с forwardRef
.
functionInput () { вернуть <вход /> } константная ссылка = useRef() <Input ref={ref} />
Функциональным компонентам не могут быть присвоены ссылки. Попытки получить доступ к этой ссылке завершится неудачей. Вы хотели использовать React.forwardRef()
forwardRef
Просмотрите исходный код ReactForwardRef.js. Сложите код, связанный с __DEV__
. Это всего лишь чрезвычайно простой компонент высокого порядка. Получите визуализированный FunctionComponent, оберните его, определите $$typeof
как REACT_FORWARD_REF_TYPE
и return
его.
Проследите код и найдитеsolveLazyComponentTag, где $$typeof
будет преобразован в соответствующий WorkTag.
WorkTag, соответствующий REACT_FORWARD_REF_TYPE
— ForwardRef. Затем ForwardRef войдет в логику updateForwardRef.
случай ForwardRef: { дочерний = updateForwardRef( нулевой, работаВ процессе, Компонент, решеноProps, рендерЛанес, ); вернуть ребенка; }
Этот метод вызовет метод renderWithHooks и передаст ref
в пятом параметре.
nextChildren = renderWithHooks( текущий, работаВ процессе, оказывать, следующийРеквизит, ref, // здесь renderLanes, );
Продолжайте отслеживать код и введите метод renderWithHooks. Вы можете видеть, что ref
будет передан в качестве второго параметра Component
. На этом этапе мы можем понять, откуда берется второй параметр ref
FuncitonComponent
обернутый forwardRef
(по сравнению со вторым параметром конструктора ClassComponent, которым является Context).
Зная, как передать ref, следующий вопрос — как назначается ref.
(назначьте RefCallback для ссылки и разорвите точку в обратном вызове). В этом методе будет оцениваться, является ли ссылка узла Fiber function
или RefObject, а также экземпляром. будут обработаны в соответствии с типом. Если узел Fiber является HostComponent ( tag = 5
), который является узлом DOM, экземпляр является узлом DOM, а если узел Fiber является ClassComponent ( tag = 1
), экземпляр является экземпляром объекта;
функция commitAttachRef(finishedWork) { вар ссылка = законченная работа.ref; если (ссылка!== ноль) { вар экземплярToUse = законченныйWork.stateNode; if (typeof ref === 'функция') { ссылка (экземплярToUse); } еще { ref.current = instanceToUse; } } }
Выше приведена логика назначения ref в HostComponent и ClassComponent. Для компонентов типа ForwardRef используются разные коды, но поведение в основном одинаковое. Здесь вы можете увидеть императивHandleEffect.
Далее мы продолжим копаться в исходном коде React, чтобы увидеть, как реализован useRef.
находит код среды выполнения useRef ReactFiberHooks, отслеживая код.
Здесь есть два метода: mountRef
и updateRef
. Как следует из названия, они соответствуют операциям над ref
, когда узел Fiber
mount
и update
.
function updateRef<T>(initialValue: T): {|current: T|} { константный крючок = updateWorkInProgressHook(); вернуть крючок.memoizedState; } function mountRef<T>(initialValue: T): {|current: T|} { константный крючок = mountWorkInProgressHook(); const ref = {текущее: начальное значение}; ook.memoizedState = ссылка; вернуть ссылку; }
Вы можете видеть, что при mount
useRef
создает RefObject
и присваивает его memoizedState
hook
. При update
он извлекается и возвращается напрямую.
Различные хуки memoizedState сохраняют различное содержимое. useState
сохраняет информацию state
, useEffect
сохраняет объекты effect
, useRef
сохраняет объекты ref
...
Методы mountWorkInProgressHook
и updateWorkInProgressHook
поддерживаются связанным списком хуков. Если связанный список не изменен, вы можете выполнить следующее. получать один и тот же объект memoizedState каждый раз, когда вы визуализируете useRef. Это так просто.
На этом этапе мы понимаем логику передачи и присвоения ref
в React, а также исходный код, связанный с useRef
. Используйте вопрос о приложении, чтобы закрепить приведенные выше знания: Внутри компонента необходимо использовать внутреннийRef HTMLInputElement
для доступа к узлу DOM
. В то же время он также позволяет внешнему компоненту ссылаться на узел. реализовать это?
const Input = frontRef((props, ref) => { const InternalRef = useRef<HTMLInputElement>(null) возвращаться ( <input {...props} ref={???} /> ) })
Подумайте, как следует писать ???
в приведенном выше коде.
============ Разделительная линия ответа ==============
Понимая внутреннюю реализацию, связанную с Ref, очевидно, что мы можем создать здесь RefCallback
, который может обрабатывать несколько. Просто назначьте ref
.
функция экспорта joinRefs<T = Any>( ссылки: Array<MutableRefObject<T null> | RefCallback<T>> ): React.RefCallback<T> { возвращаемое значение => { refs.forEach(ref => { if (typeof ref === 'функция') { ссылка (значение); } else if (ref !== null) { ref.current = значение; } }); }; } const Input = frontRef((props, ref) => { const InternalRef = useRef<HTMLInputElement>(null) возвращаться ( <input {...props} ref={combineRefs(ref, InternalRef)} /> ) })