Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add forwardRef API to make it possible let component handle ref itself #258

Open
Jokcy opened this issue Jan 24, 2021 · 15 comments
Open

Add forwardRef API to make it possible let component handle ref itself #258

Jokcy opened this issue Jan 24, 2021 · 15 comments

Comments

@Jokcy
Copy link

Jokcy commented Jan 24, 2021

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} />
  }
})
@Jokcy
Copy link
Author

Jokcy commented Feb 5, 2021

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"?

@leopiccionia
Copy link

leopiccionia commented Feb 9, 2021

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/

@Jokcy
Copy link
Author

Jokcy commented Feb 19, 2021

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/

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.

@leopiccionia
Copy link

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.

@Jokcy
Copy link
Author

Jokcy commented Feb 22, 2021

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.

@agileago
Copy link

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

@DmitriiBaranov-NL
Copy link

DmitriiBaranov-NL commented May 10, 2022

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

@orimay
Copy link

orimay commented May 23, 2022

So far I use

  <textarea
    :ref="el => emit('e-ref', el as HTMLTextAreaElement)"
    ...

and

  <CTextArea
    @e-ref="el => (elDetails = el)"
    ...

@hcg1023
Copy link

hcg1023 commented Jul 19, 2022

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

@hcg1023
Copy link

hcg1023 commented Jul 19, 2022

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

@Thy3634
Copy link

Thy3634 commented Jul 19, 2022

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.

SFC playground link

@crush2020
Copy link

I think it's good to keep the original. value writing

@sadeghbarati
Copy link

sadeghbarati commented Dec 17, 2023

React will Deprecate forwardRef API

Replacement is to add ref as prop to the Component

@andrey-hohlov
Copy link

@sadeghbarati can you please give a proof link?

@sadeghbarati
Copy link

https://twitter.com/acdlite/status/1719496241501847884?t=A3qNDupM9tnJq7mRG589pg&s=19

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

10 participants