Как передать более 1 ссылки дочернему компоненту в SolidJS?
Родительский компонент:
function ParentComponent() {
return (
<section>
<ChildComponent ref={sectionRef} ref1={headerRef} />
</section>
);
}
Дочерний компонент:
function ChildComponent(props) {
return (
<section ref={props.ref}>
<article>
<h2 ref={props.ref1}>Lorem Ipsum</h2>
<p>Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime mollitia,
molestiae quas vel sint commodi repudiandae consequuntur voluptatum laborum
numquam blanditiis harum quisquam eius sed odit fugiat iusto fuga praesentium optio, eaque rerum!</p>
</article>
</section>
);
}
Моя цель состоит в том, чтобы иметь возможность ориентироваться на разные элементы DOM в дочернем компоненте из родительского компонента, чтобы я мог анимировать их на основе события прокрутки от родителя.
Я пытался передать ссылки как разные структуры данных:
<ChildComponent ref={{sectionRef, headerRef}} />
а также:
<ChildComponent ref={[sectionRef, headerRef]} />
а также:
<ChildComponent section={sectionRef} header={headerRef} />
Но постоянно получаю ошибки, что 2-й ref не определен. Я могу заставить его работать, только если я передам один ref для каждого дочернего компонента. Любые идеи?
Ссылки на справочные материалы, которые я просмотрел: https://www.solidjs.com/tutorial/bindings_forward_refs https://www.solidjs.com/docs/latest/api#ref
2 ответа
Когда Refs установлены в компонентах
Есть 3 способа сослаться на элемент DOM внутри компонента.
- Передать обычную переменную
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl) // logs button element
})
return (
<button ref={buttonEl}>Click</button>
);
}
- Установщик сигналов пропуска
function Component() {
const [buttonEl, setButtonEl] = createSignal(null);
onMount(() => {
console.log(buttonEl()) // logs button element
})
return <button ref={setButtonEl}>Click</button>;
}
- Пропустить обратный вызов
function Component() {
let buttonEl;
const refCallback = (el) => {
buttonEl = el;
};
onMount(() => {
console.log(buttonEl); // logs button element
});
return <button ref={refCallback}>Click</button>;
}
Когда Refs установлены в дочерних компонентах
Однако при ссылке на элемент DOM, установленный в дочерних компонентах, ситуация иная. Для демонстрации мы не будем использовать prop для дочернего компонента, а вместо этого будем использовать prop.
- Передать обычную переменную в . Переменная не обновляется и
undefined
.
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl); // logs `undefined`
});
return <Child PASSREF={buttonEl} />;
}
function Child(props) {
return <button ref={props.PASSREF}>Click</button>;
}
- Передайте установщик сигналов в , работает.
function Component() {
const [buttonEl, setButtonEl] = createSignal(null)
onMount(() => {
console.log(buttonEl()); // logs button element
});
return <Child PASSREF={setButtonEl} />;
}
function Child(props) {
return <button ref={props.PASSREF}>Click</button>;
}
- Передайте обратный вызов, который объявлен в той же области, что и , в
PASSREF
, работает.
function Component() {
let buttonEl;
const refCallback = (el) => {
buttonEl = el;
};
onMount(() => {
console.log(buttonEl); // logs button element
});
return <Child PASSREF={refCallback} />;
}
function Child(props) {
return <button ref={props.PASSREF}>Click</button>;
}
Чтобы исправить решение № 1, в котором вы используете обычную переменную
let buttonEl;
, вы используете правильное свойство компонента, чтобы установить элемент в переменную.
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl); // logs button element
});
return <Child ref={buttonEl} />;
}
function Child(props) {
return <button ref={props.ref}>Click</button>;
}
Так почему же это работает? Хорошо, потому что в скомпилированном выводе аргумент Child prop, где используется ref, фактически заменяется встроенным обратным вызовом, таким образом, он живет в той же области, где объявлен и может быть обновлен.
// This is NOT how the Compiled Output actually looks,
// but ref argument is replaced by an inline callback
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl); // logs button element
});
return <Child ref={(el) => buttonEl = el} />;
}
function Child(props) {
return <button ref={props.ref}>Click</button>;
}
Разве это не выглядит знакомо? Это структурировано почти точно по решениям № 3, где вы передаете функцию обратного вызова для обновления
buttonEl
.
Решение
Честно говоря, это зависит от вашего варианта использования, либо используйте установщики сигналов, от
createSignal
, для передачи ссылок или используйте функции обратного вызова, объявленные в родительском элементе, для установки простых переменных.
В этом примере решения и являются неназначенными переменными. передается в реквизит, где за кулисами он завернут в обратный вызов. Функция обратного вызова
refCallback
передается
ref1
опора, где она установлена
headerRef
к переданному значению элемента.
function ParentComponent() {
let sectionRef;
let headerRef;
const refCallback = (el) => {
headerRef = el
}
onMount(() => {
console.log(sectionRef); // logs section el
console.log(headerRef); // logs header el
});
return (
<section>
<Overview ref={sectionRef} ref1={refCallback} />
</section>
);
}
function Overview(props) {
return (
<section ref={props.ref}>
<article>
<h2 ref={props.ref1}>Lorem Ipsum</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Maxime
mollitia, molestiae quas vel sint commodi repudiandae consequuntur
voluptatum laborum numquam blanditiis harum quisquam eius sed odit
fugiat iusto fuga praesentium optio, eaque rerum!
</p>
</article>
</section>
);
}
Как
Чтобы еще раз повторить. Способ, которым Solid заставляет это работать, заключается в том, что в скомпилированном выводе, если свойство компонента имеет имя, оно заменяется методом объекта (в этом контексте имеет ту же стратегию, что и функция обратного вызова), который находится в том же месте, где " ref" (например,
sectionRef
), таким образом, ему может быть назначена переменная "ref".
Если вам интересно, вот фактический скомпилированный вывод решения, где вы можете увидеть, как
ref
на самом деле выглядит.
// Compiled Output
function ParentComponent() {
let sectionRef;
let headerRef;
const refCallback = el => {
headerRef = el;
};
// ...
return (() => {
const _el$ = _tmpl$.cloneNode(true);
insert(_el$, createComponent(Overview, {
ref(r$) {
const _ref$ = sectionRef;
typeof _ref$ === "function" ? _ref$(r$) : sectionRef = r$;
},
ref1: refCallback
}));
// ...
})();
}
Поскольку вы хотите взаимодействовать с дочерними элементами в родительской прокрутке, что не имеет ничего общего с состоянием, вы можете пропустить всю церемонию и получить доступ к дочерним элементам напрямую, воспользовавшись тем, что Solid компилируется в собственный JavaScript.
import { render } from 'solid-js/web';
const App = () => {
const handleScroll = (event: any) => {
const header = event.currentTarget.querySelector('header');
const section = event.currentTarget.querySelector('section');
console.log(header, section);
};
return (
<div
style={`position: fixed; overflow: scroll; width: 100vw; height: 100vh`}
onScroll={handleScroll}
>
<header style={`height: 500px`}>Header</header>
<section style={`height: 500px`}>Section</section>
</div>
)
};
render(App, document.body);
Живая демонстрация: https://playground.solidjs.com/anonymous/19a789fe-d710-40ba-8b3e-a256ab23c1e2
Видите ли, вам не нужно все это извращенное мышление React.
Если ваш дочерний компонент каким-то образом зависит от состояния родителя, т.е.y
position, лучше всего передать родительское состояние как свойство и добавить событие внутри дочернего тела:
import { render } from 'solid-js/web';
import { Accessor, Component, createEffect, createSignal } from 'solid-js';
const Child: Component<{ y: Accessor<number> }> = (props) => {
createEffect(() => {
console.log(props.y());
});
return <div>Child Component</div>
}
const App = () => {
const [y, setY] = createSignal<number>(0);
setInterval(() => {
setY(v => v + 1);
}, 1000);
return (
<div>
<Child y={y} />
</div>
)
};
render(App, document.body);
https://playground.solidjs.com/anonymous/55405c86-b811-4ef7-8cfc-788f74ea0681
Если состояние является объектом, но вам нужно только одно свойство, вы можете получить состояние:
// interface State { x: number, y: number }
const y = () => state().y;
// Then pass it as a prop
<Child y={y} />
Ссылки предназначены для доступа к элементу внутри тела компонента для таких вещей, как добавление и удаление прослушивателей событий. Не раздавайте их, если можете, и уж точно не злоупотребляйте ими. Поскольку state+effects обеспечивает предсказуемость, ссылки приносят хаос.