如何在 SolidJS 中将多个 ref 传递给子组件?

  • 本文关键字:组件 ref SolidJS solid-js
  • 更新时间 :
  • 英文 :


父组件:

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} />

但是不断收到第二个引用未定义的错误。只有当我为每个子组件传递一个 ref 时,我才能让它工作。有什么想法吗?

我看过的参考资料的链接: https://www.solidjs.com/tutorial/bindings_forward_refs https://www.solidjs.com/docs/latest/api#ref

当引用在组件中设置时

有 3 种方法可以在组件中引用 DOM 元素。

  1. 传递常规变量
function Component() {
let buttonEl;
onMount(() => {
console.log(buttonEl) // logs button element
}) 
return (
<button ref={buttonEl}>Click</button>
);
}
  1. >通过信号设定器
function Component() {
const [buttonEl, setButtonEl] = createSignal(null);
onMount(() => {
console.log(buttonEl()) // logs button element
}) 
return <button ref={setButtonEl}>Click</button>;
}
  1. 传递回调
function Component() {
let buttonEl;
const refCallback = (el) => {
buttonEl = el;
};
onMount(() => {
console.log(buttonEl); // logs button element
});
return <button ref={refCallback}>Click</button>;
}

在子组件中设置引用时

但是,当引用在子组件中设置的 DOM 元素时,情况会有所不同。为了演示,我们不会对 Child 组件使用refprop,而是PASSREFprop。

  1. 将常规变量传递给PASSREF。变量不会更新,并且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>;
}
  1. 将信号设置器传递到PASSREF,有效。
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>;
}
  1. 传递回调,即在与buttonEl相同的范围内声明,以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>;
}

要修复使用常规变量let buttonEl;的 #1 解决方案,请使用正确的组件 propref以便将元素设置为变量。

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>;
}

那么为什么会这样呢?好吧,因为在编译的输出中,使用 ref 的 Child prop 参数实际上被内联回调替换,这样它就存在于声明buttonEl并且可以更新的同一范围内。

// 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,传递 refs,或使用父级中声明的回调函数来设置您的普通变量。

在此解决方案示例中,sectionRefheaderRef都是未赋值的变量。sectionRef被传递给ref道具,在幕后,它被包装在回调中。回调函数refCallback传递给 propref1在那里它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,则将其替换为位于创建"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位置,最好将父组件的状态作为 prop 传递,并在子组件的体内添加事件:

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} />

引用用于访问组件主体内的元素,例如添加和删除事件侦听器。如果你能帮助它,不要把它们传来传去,当然也不要过度使用它们。因为状态+效果带来可预测性,裁判带来混乱。

最新更新