Recently, I had the honor to participate in the development of devUI open source component library and started to get into vUE. After using vuE3 + TSX for a while, it was fun and comfortable.

In the development process, I often need to find information from the Internet, learn from the team’s internal code, and learn as I go along. The general feeling is that the use of JSX in VUE, primarily to replace Template, is just a stylistic change. I also (first out of habit) wanted to bring one-way data flows, functional components, and so on from React to VUE.

In this context, combined with the specific situation of devUI project, after a period of exploration, finally formed a complete set of VUE3 + TSX component writing method, to share with you, hoping to inspire.

Here I also want to emphasize that the introduction of one-way data flow and functional components in VUE is only for the purpose of discussing technical possibilities and hoping to enrich the development mode of VUE, regardless of the strengths and weaknesses of the framework. Also, since vuE3 has many experimental features, it is recommended to take a relatively conservative approach in production.

A common way to write functional components

The code form of a component should look like this

// hello.jsx
export default ({ value = 'Hello Vue'}) = ><h1>{value}</h1>
Copy the code

To play down the differences in the framework, there are noneimport React.

Now, one of the nice things about functional notation is that we can use higher order functions directly.

// main.jsx
import Hello from './hello.jsx'
export default ({ name = 'JS old dog'. rest }) =><div><Hello { . rest} / ><h3>by {name}</h3></div>
Copy the code

Compared with class inherited component reuse, high order reuse can avoid too long prototype chain, and attribute transfer is flat.

Vue3 defineComponent+JSX

Get a feel for how vuE3 components are written

import { defineComponent } from 'vue'
export default defineComponent({
    props: {
        value: { type: String.default: 'Hello Vue3'}},setup(props) {
        // Don't do that here, if you do that you're going to be a closure variable, out of the response.
        // const { value = 'Hello Vue3' } = props || {}
        return () = > {
            // Return the props inside the render function
            const { value = 'Hello Vue3' } = props || {}
            return <h1>{value}</h1>}}})Copy the code

The vue component in this example uses object declarations, and in the simplest case includes props and setup tags, where setup returns the render function, so you don’t need to redeclare render — this is only for VUE3.

props

Props is a declaration of the interface to the component, where

  • typeRepresents the type, but notice the use ofConstructorA constructor that represents the types, that is, the types of JS native data.
  • defaultRepresents the default value
  • requiredIndicates whether the property is required.

setup

Setup here is actually a higher-order function, which is the closure of Render. This is not used throughout the process, indicating that this part can be context-free and potentially decoupled.

Compile functional components of JSX with Vue

Let’s start with webpack to get a feel for the compilation process.

The React project uses babel-loader to process JS files, and mounts presets and plugins dedicated to React to support JSX.

Webpack support

In fact, Vue has a corresponding loader to support JSX: @vue/babel-plugin-jsx

Loader support JSX

Let’s change the JSX rule configuration:

{
    test: /\.jsx? $/,
    use: {
        loader: 'babel-loader'.options: {
            plugins: ['@vue/babel-plugin-jsx'],}},exclude: /node_modules/,}Copy the code

Don’t ask me why I don’t have presets. This configuration is manual and works.

After this configuration, JSX functional components can be correctly compiled as VUE components.

Loader support TSX

Previously, you only need a TS-Loader.

It is speculated that VUE does not officially support TSX, JSX was originally just a replacement for Template, so there is no clue about TSX and no relevant information can be found.

JSX: < span style = “box-sizing: border-box! Important; word-wrap: inherit! Important;”

{
    test: /\.tsx? $/,
    use: [{
        loader: 'babel-loader'.options: {
            plugins: ['@vue/babel-plugin-jsx'],}},'ts-loader'].exclude: /node_modules/,}Copy the code

This piece of code, which looks simple, actually took me over two hours.

Stateless component

It’s very simple:

export default ({ value = 'Hello Vue'}) = > (<div>
        <h1>{value}</h1>
        <button>click me</button>
    </div>
)
Copy the code

Built-in state component

In React, the built-in states of functional components are implemented through useState, and the life cycle is weakened, replaced by useEffect based on state update driver. Vue also has a series of lifecycle hooks, such as Reactive and onMouted onUpdated.

React’s built-in state implementation

In React, the state machine and return components are in the same function domain:

export default ({ value = 'Hello Vue'= > {})const [count, setCount] = useState(0)
    return (
        <div>
            <h1>{value}</h1>
            <button onClick={()= > setCount(count + 1)}>
                click me {count}
            </button>
        </div>)}Copy the code

Vue built-in state implementation

While vue can’t do this yet, we can mimic setup by enclosing the state machine in a closure:

const factory = () = > {

    const state = reactive({
        count: 0
    })
    
    return ({ value = 'Hello Vue' }) = > (
        <div>
            <h1>{value}</h1>
            <button onClick={()= > state.count += 1}>
                click me {state.count}
            </button>
        </div>)}export default factory()
Copy the code

Very elegant, very effective.

Strongly typed TSX aligned props

The functional component is the decoupling of setup and now the ability to align props.

Props and state type declarations

React: props and state are all components in the core.

typeTProps = { value? :string
}

typeTState = { count? :number
}
Copy the code

With these two types, we implement the props type and required declarations, and the rest of the default can be implemented in the program, such as default when destructing, short-circuit, etc.

const { value = 'abc' } = props
Copy the code

Code minor refactoring

const factory = () = > {
    const state = reactive<TState>({
        count: 0
    })

    return (props: TProps) = > {
        const { value = 'Hello Vue' } = props
        const handleClick = () = > {
            state.count = (state.count || 0) + 1
        }
        return (
            <div>
                <h1>{value}</h1>
                <button onClick={handleClick}>click me [{state.count}]</button>
            </div>)}}export default factory()
Copy the code

The compilation passed and the operation is normal.

The key code

webpack.config.js

const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
    mode: 'production'.// mode: 'development',
    entry: './test1/index.jsx'.output: {
        path: path.resolve(__dirname, './dist/test1'),
        filename: 'index.js',},plugins: [
        new CleanWebpackPlugin(),
        new HtmlWebpackPlugin({
            template: './test1/index.html'.filename: './index.html'.inject: 'body'}),].resolve: {
        extensions: ['.tsx'.'.ts'.'.jsx'.'.js'].alias: {}},module: {
        rules: [{test: /\.jsx? $/,
                use: {
                    loader: 'babel-loader'.options: {
                        plugins: ['@vue/babel-plugin-jsx'],}},exclude: /node_modules/}, {test: /\.tsx? $/,
                use: [{
                    loader: 'babel-loader'.options: {
                        plugins: ['@vue/babel-plugin-jsx'],}},'ts-loader'].exclude: /node_modules/}},],devServer: {
        compress: true.port: 9001,}}Copy the code

Project entry file

// index.tsx
import { createApp } from 'vue'
import Page from './page'

createApp({
    render: () = > <Page />
}).mount('#app')
Copy the code

Afterword.

As mentioned above, JSX is probably just an alternative to Template, and I might be writing function components in JSX instead of the original purpose.

During the development process, there was some disagreement over whether className or class, but it was finally unified as class. The advice I’ve been given is, don’t sweat the details. But if Vue does come up with a personalized way of writing JSX that is not developer-friendly, that may not be a good thing.

The life cycle uses the ON hooks provided by VUE, but some event bindings have different names, such as onMouseenter instead of onMouseenter, and have not been extensively tested.

The above.