rfcs
rfcs copied to clipboard
Add forwardRef API to make it possible let component handle ref itself
When we pass ref to a component or DOM element, the ref will point to component instance or DOM element which handled by Vue internally.
In some case we may want to handle ref ourself. For example, if I want wrapper a Input component but just want to handle some logic when use input. I maight do:
const Input = defineComponent({
setup() {
const localHandleInput = () => {}
return () => <input onInput={localHandleInput} />
}
})
At this case I don't want the ref passed to the component just point to the component instance because it's nothing there. I want to point the ref to the real input DOM element, by now I did not find any way to implement this.
Maybe we need to add some api like forwardRef
in React, or add an forwardRef
option when defineComponent. Then Vue pass the ref handler into the setup
method, so that we can handle ref ourself.
const Input = defineComponent({
forwardFef: true,
setup(props, { ref }) {
const localHandleInput = () => {}
return () => <input onInput={localHandleInput} ref={ref} />
}
})
https://vue3js.cn/docs/zh/guide/component-template-refs.html#%E6%A8%A1%E6%9D%BF%E5%BC%95%E7%94%A8 https://vue3js.cn/docs/zh/guide/composition-api-template-refs.html#%E6%A8%A1%E6%9D%BF%E5%BC%95%E7%94%A8
As these two documents said, I think vue ref is different from react ref.
React ref on a component could be forwarded to child element inside child component. So we can access child element by ref.
But we can not do that in vue. In parent component we could access child component by ref. And then we could call child component methods by ref. We should also define ref inside child component if we need manipulate the input element inside child component.
And I think vue ref is better than react ref. 😄
This issue is to talk about we need to add the forwardRef
ability to vue, it's not talking about if vue already have this ability.
And what do you mean by "vue ref is better than react ref"?
The biggest challenge about manually implementing a behavior similar to forwardRef
in Vue, currently, is the fact that refs returned by setup
are unwrapped inside template (docs).
However, as mentioned in vuejs/composition-api#317, you can pass a closure as a prop to work around that: https://jsfiddle.net/leopiccionia/28ghm1et/
It can even be refactored into an util function: https://jsfiddle.net/leopiccionia/s1vpu2f3/
The second biggest challenge is that you can't have a prop named ref
, but the workaround is just using a different name.
However, I'm not sure if something like forwardRef
is very Vue-y. I'd rather pass the raw element as payload of the re-emitted event, and access it in the event callback.
https://jsfiddle.net/leopiccionia/3rofa06n/
The biggest challenge about manually implementing a behavior similar to
forwardRef
in Vue, currently, is the fact that refs returned bysetup
are unwrapped inside template (docs).However, as mentioned in vuejs/composition-api#317, you can pass a closure as a prop to work around that: https://jsfiddle.net/leopiccionia/28ghm1et/
It can even be refactored into an util function: https://jsfiddle.net/leopiccionia/s1vpu2f3/
The second biggest challenge is that you can't have a prop named
ref
, but the workaround is just using a different name.However, I'm not sure if something like
forwardRef
is very Vue-y. I'd rather pass the raw element as payload of the re-emitted event, and access it in the event callback. https://jsfiddle.net/leopiccionia/3rofa06n/
Yeah, we do will face these problems. But the forwardRef
API is not about if we can get the element, it's about what behavior ref
prop will do. The mostly common pattern using forwardRef
in react is called HOC, for example I have an HOC to log some props when using an component:
const NewButton = logProp('type')(Button)
logProp
is an HOC and it return NewButton
as a component, when using NewButton
it will log type
prop everytime. In this example NewButton
is just Button
will log, but if we do not forwardRef
when people put ref
on NewButton
, they will get notthing since it is a functional component.
As the developer of logProp
, we want the ref
prop can be passed to the real Button
component or othe component pass to the logProp
HOC. And this is what forwardRef
for.
Yeah, we do will face these problems. But the forwardRef API is not about if we can get the element, it's about what behavior ref prop will do. The mostly common pattern using forwardRef in react is called HOC (...)
Vue already handles the forwarding of the ref
attribute in functional components, no matter the level of indirection.
https://jsfiddle.net/leopiccionia/teh05k9v/
The part that Vue still doesn't support natively is letting the user manually handle the ref
attribute. That's where you need the workarounds cited.
Yeah, we do will face these problems. But the forwardRef API is not about if we can get the element, it's about what behavior ref prop will do. The mostly common pattern using forwardRef in react is called HOC (...)
Vue already handles the forwarding of the
ref
attribute in functional components, no matter the level of indirection. https://jsfiddle.net/leopiccionia/teh05k9v/The part that Vue still doesn't support natively is letting the user manually handle the
ref
attribute. That's where you need the workarounds cited.
Due to the limitation of functional component, it only solve part of problem.
@Jokcy maybe we can use expose to achieve it.
function useForwardRef() {
const instance = getCurrentInstance()!
function handleRefChange(realRef: any) {
instance.exposed = realRef
instance.exposeProxy = realRef
}
return handleRefChange
}
the example:
const A = defineComponent({
props: {
size: String as PropType<'small' | 'large' | 'middle'>,
},
setup(props, ctx) {
const originExpose = {
focus() {
console.log('focus')
},
}
ctx.expose(originExpose)
return () => <div>my size is {props.size}</div>
},
})
function createSizeA(size: 'small' | 'large' | 'middle') {
return defineComponent((props, ctx) => {
const handleRef = useForwardRef()
return () => <A {...ctx.attrs} size={size} ref={handleRef}></A>
})
}
const LargeA = createSizeA('large')
export default class HelloWorldView extends VueComponent {
aRef = ref()
@Hook('Mounted')
mounted() {
this.aRef.value?.focus()
console.log(this.aRef.value)
}
render() {
return <LargeA ref={this.aRef}></LargeA>
}
}
Perhaps, instead there could be both e-ref
(for element ref) and c-ref
(for component ref)
The issue with having e-ref
on a component is that a component may not have a single root component. But for such case, it's e-ref
could either be bound to all the roots at once as an array or it could be forbidden on a multi-root component (Or we could introduce yet another m-ref
or something for this edge case).
Introducing e-ref
and c-ref
next to ref
would keep this change backwards-compatible. I'm not sure if it worths adding this whole system all the efforts and complexity, but on the other hand, it would introduce a clear distinction between two different concepts -- element and component reference. Also, I believe it'd be better to have ref bindings rather than magic string references to ref variables by name, and they should behave as v-model:
<MyComponent e-ref="elMyComponent" c-ref="cmpMyComponent" />
<component :name="genericComponentName" e-ref="elGenericComponent" c-ref="cmpGenericComponent" />
So far I use
<textarea
:ref="el => emit('e-ref', el as HTMLTextAreaElement)"
...
and
<CTextArea
@e-ref="el => (elDetails = el)"
...
I would also like to add a forwardRef method, so that UI components can be unified to expose DOM operations, or have an extra property like C-ref, but it has to be official, which would be a great help in developing a Vue library
Especially when using the Script Setup syntax, we don't expose anything by default, but as a consumer, I can't expect all components to expose me a DOM
I believe that child component should control itself's expose, but if there is only one root element, "custom element" may help.
There is a example to use "expose" or "custom directive" to get root element.
I think it's good to keep the original. value writing
React will Deprecate forwardRef
API
Replacement is to add ref
as prop to the Component
@sadeghbarati can you please give a proof link?
https://twitter.com/acdlite/status/1719496241501847884?t=A3qNDupM9tnJq7mRG589pg&s=19