This is the second in a series of Web Components that will cover integration with existing JS frameworks such as React and Vue.
Here are links to the three articles:
- Web Components Series I – Basic Concepts
- Web Components series 2 – Integration with existing front-end frameworks
- Web Components series (Part 3) – Introduction to relevant Open Source Libraries (Stencil and Lit)
Since Web Components are mostly used to develop UI component libraries, they may end up being used in popular front-end framework development projects. Custom-elements everywhere shows the integration of Web Components in various frameworks and runs some test cases on how to pass data and listen for events, which are the two main points we focus on when developing Components. And they scored it. Vue(2.x) and Angular both passed the test cases, while React received low scores.
The following describes how to use it in several frameworks.
Vue
The official Vue documentation has this description:
You may have noticed that Vue components are very similar to Custom Elements, which are part of the Web Components Spec. That’s because Vue’s component syntax is loosely modeled after the spec.
The Vue component follows some of the custom component specifications, so the integration with Web Components is high. Post the test results:
To explain:
How to pass data
There is nothing special about string data, but for other types of data, voo2.x provides a.prop modifier that converts data passed from attribute to property.
<my-comp :list.prop="list"></my-comp>
Copy the code
But I tried it in Vue3, and the modifier seems to fail. You can get the custom element from ref and set it:
<my-comp ref="myComp"></my-comp>
Copy the code
this.$refs.myComp.list = []
Copy the code
How to listen for Events
Because Vue uses built-in browser events internally, events thrown by Custom elements can be listened to. Just like any other Vue component, it listens with v-ON or @ for short.
other
Using Web Components in Vue also requires configuring Vue to ignore custom elements. Do not treat them as Vue Components, or you will be prompted that they are unregistered Components. Do this in an entry file such as main.js:
// main.js
// Vue2.X
Vue.config.ignoredElements = [/^my-/] // Assume that all custom elements begin with my-
// Vue3
app.config.isCustomElement = tag= > tag.startsWith('my-')
Copy the code
MyButton, MyDropdown, and MyDialog are the three custom components that vue2.x uses as follows:
<template>
<div>
<my-button :label="buttonLabel" @clicked="clickBtnHandler"></my-button>
<my-dialog
:dialogTitle.prop="dialogTitle"
:visible.prop="showDialog"
@close="closeHandler"
@confirm="confirmHandler">
Hello Vue!
</my-dialog>
<my-dropdown placeholder="Please select" :data.prop="hobbies" :defaultValue.prop="defaultValue"></my-dropdown>
</div>
</template>
<script>
/* eslint-disable*/
export default {
name: 'App'.data() {
return {
buttonLabel: 'Open Dialog'.dialogTitle: 'Welcome to Vue'.showDialog: false.hobbies: [{label: 'Table tennis'.value: 'table tennis'
},
{
label: 'read'.value: 'reading'}].defaultValue: 'reading'}},methods: {
closeHandler() {
this.showDialog = false
},
confirmHandler() {
console.log('confirm')},clickBtnHandler() {
this.showDialog = true}}}</script>
Copy the code
React
Integration problem analysis
Using Web Components in React can be tricky because of two factors:
- When the data is passed through the Attributes of the custom (not react Component), react will be completely converted to strings and arrays will become
.
The concatenated string, the object type will become[object Object]
Non-string data fails to be passed. Unlike Vue offers.prop
This approach translates to property passing. - React implements its own event system, so it can’t listen for events thrown from within custom components (which are browser built-in events).
The following is the evaluation result, which shows that the integration is not very good:
But there is no way, it is relatively tedious.
The solution
Create a React Component and bundle the Web Component. The Other React components only talk to the Wrapper Component. The Wrapper Component then receives the props and passes the data to the Web Component. The main way to do this is to retrieve the Web Component instance via the REF and then pass the props data to the Property of the Web Component instance at the appropriate lifecycle. You can also listen for events thrown internally on the Web Component instance.
Here’s a simple example of how to use the React Class Component and hooks methods:
- Class Component
// MyButtonWrapper.js
import React, { Component } from 'react';
import './MyButton';
export default class MyButtonWrapper extends Component {
constructor(props) {
super(props)
this.buttonRef = React.createRef()
}
componentDidMount() {
this.buttonRef.current.label = this.props.label;
if (this.props.onClicked) {
this.buttonRef.current.addEventListener('clicked'.(e) = > {
this.props.onClicked(e)
})
}
}
render() {
return(
<my-button ref={this.buttonRef}></my-button>)}}Copy the code
// MyDialogWrapper.js
import React, { Component } from 'react';
import './MyDialog';
export default class MyDialogWrapper extends Component {
constructor(props) {
super(props);
this.dialogRef = React.createRef();
}
componentDidMount() {
const {dialogTitle, visible} = this.props
this.dialogRef.current.dialogTitle = dialogTitle
this.dialogRef.current.visible = visible
if (this.props.onConfirm) {
this.dialogRef.current.addEventListener('confirm'.(e) = > {
this.props.onConfirm(e)
})
}
if (this.props.onCancel) {
this.dialogRef.current.addEventListener('cancel'.(e) = > {
this.props.onCancel(e)
})
}
if (this.props.onClose) {
this.dialogRef.current.addEventListener('close'.(e) = > {
this.props.onClose(e)
})
}
}
componentDidUpdate(prevProps, prevState) {
if (this.props.dialogTitle ! == prevProps.dialogTitle) {this.dialogRef.current.dialogTitle = this.props.dialogTitle
}
if (this.props.visible ! == prevProps.visible) {this.dialogRef.current.visible = this.props.visible
}
}
render() {
return (
<my-dialog ref={this.dialogRef}>{this.props.children}</my-dialog>)}}Copy the code
Reference these two Wrapper components in app.js:
import React, { Component } from 'react';
import MyButtonWrapper from './web-components/MyButtonWrapper';
import MyDialogWrapper from './web-components/MyDialogWrapper';
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
label: 'Open Dialog'.title: 'Welcome'.visible: false}}clickButtonHandler() {
this.setState({
visible: true})}confirmHandler() {
console.log('Dialog confirm')}cancelHandler() {
console.log('Dialog cancel')}closeHandler() {
this.setState({
visible: false})}render() {
const {label, title, visible} = this.state
return (
<div className="App">
<MyButtonWrapper label={label} onClicked={this.clickButtonHandler.bind(this)}></MyButtonWrapper>
<MyDialogWrapper
dialogTitle={title}
visible={visible}
onConfirm={this.confirmHandler.bind(this)}
onCancel={this.cancelHandler.bind(this)}
onClose={this.closeHandler.bind(this)}>
Hello Webcomponents!
</MyDialogWrapper>
</div>); }}Copy the code
- Hooks
// MyButtonWrapperHook.js
import {useRef, useEffect} from 'react';
import './MyButton';
export default function MyButtonWrapperHook(props) {
const buttonRef = useRef(null)
function clickButtonHandler(e) {
props.onClicked(e)
}
useEffect(() = > {
buttonRef.current.label = props.label
if (props.onClicked) {
buttonRef.current.addEventListener('clicked', clickButtonHandler)
}
return () = > {
buttonRef.current.removeEventListener('clicked', clickButtonHandler)
}
})
return (
<my-button ref={buttonRef}></my-button>)}Copy the code
// MyDialogWrapperHook.js
import {useRef, useEffect} from 'react';
import './MyDialog';
export default function MyDialogWrapperHook(props) {
const dialogRef = useRef(null)
function onConfirmHandler(e) {
props.onConfirm(e)
}
function onCancelHandler(e) {
props.onCancel(e)
}
function onCloseHandler(e) {
props.onClose(e)
}
useEffect(() = > {
dialogRef.current.dialogTitle = props.dialogTitle
dialogRef.current.visible = props.visible
if (props.onConfirm) {
dialogRef.current.addEventListener('confirm', onConfirmHandler)
}
if (props.onCancel) {
dialogRef.current.addEventListener('cancel', onCancelHandler)
}
if (props.onClose) {
dialogRef.current.addEventListener('close', onCloseHandler)
}
return () = > {
dialogRef.current.removeEventListener('confirm', onConfirmHandler)
dialogRef.current.removeEventListener('cancel', onCancelHandler)
dialogRef.current.removeEventListener('close', onCloseHandler)
}
})
return (
<my-dialog ref={dialogRef}>{props.children}</my-dialog>)}Copy the code
Use these two Wrapper components:
import { useState } from 'react';
import MyButtonWrapperHook from './web-components/MyButtonWrapperHook';
import MyDialogWrapperHook from './web-components/MyDialogWrapperHook';
export default function App() {
const [label, setLabel] = useState('Open Dialog! ')
const [title, setTitle] = useState('Welcome')
const [visible, setVisible] = useState(false)
const [list, setList] = useState([
{
label: 'read'.value: 'reading'
},
{
label: 'King of Glory'.value: 'nongyao'}])return (
<div>
<MyButtonWrapperHook label={label} onClicked={()= > setVisible(true)}></MyButtonWrapperHook>
<MyDialogWrapperHook
dialogTitle={title}
visible={visible}
onClose={()= > setVisible(false)}>
Hello web components in React with hooks
</MyDialogWrapperHook>
</div>)}Copy the code
Angular
Angular is also highly integrated and has passed all test cases, which I will not elaborate on here because I am not familiar with Angular.
reference
- Use Web Components in Vue 2 and 3 + Composition API
- Using Web Components in React
- Using Web Components in a React Application