Let’s play with JSX in Vue: let you master its features at once!

Click on the front-end Q above and follow the public account

Reply to join the group and join the front-end Q technology exchange group

Introduction to JSX

JSX is a syntax extension of Javascript, which has all the functions of Javascript while having the semantics and intuitiveness of html. It allows us to write template syntax in JS:

const el = <div>Vue 2</div>;
Copy code

The above code is neither HTML nor a string. It is called JSX, which is an extended syntax of JavaScript. JSX may remind you of template syntax, but it has the full programming power of Javascript.

When to use JSX

When starting to write a component that can only dynamically generate a heading through the level prop, you may quickly think of implementing it like this:

<script type="text/x-template" id="anchored-heading-template">
<h1 v-if="level === 1">
    <slot></slot>
</h1>
<h2 v-else-if="level === 2">
    <slot></slot>
</h2>
<h3 v-else-if="level === 3">
    <slot></slot>
</h3>
</script>
Copy code

Using a template here is not the best choice. Part of the code is repeatedly written in the titles of each level, which is not concise and elegant enough. If you try to write it in JSX, the code will become much simpler:

const App = {
  render() {
    const tag = `h${this.level}`
    return <tag>{this.$slots.default}</tag>
  }
}
Copy code

Or if you have written a lot of render functions, you may find the following code to be painful to write:

createElement(
    'anchored-heading', {
        props: {
            level: 1
        }
    }, [
    createElement('span', 'Hello'),
        'world!'
    ]
)
Copy code

Especially when the corresponding template is so simple:

<anchored-heading :level="1">
    <span>Hello</span> world!
</anchored-heading>
Copy code

At this time, you can use JSX syntax in Vue, which allows us to return to a syntax closer to templates:

import AnchoredHeading from './AnchoredHeading.vue'
  
newVue({
    el: '#demo',
    render: function (h) {
        return (
            <AnchoredHeading level={1}>
                <span>Hello</span> world!
            </AnchoredHeading>
        )
    }
})
Copy code

During the development process, the message prompt component Message is often used. One possible way to write it is as follows:

Message.alert({
  message: 'Are you sure you want to delete?',
  type: 'warning'
})
Copy code

But I hope that message can customize some styles. At this time, you may need to make Message.alert support JSX (of course you can also use slots /html and other methods to solve)

Message.alert({
  messge: <div>Are you sure you want to delete <span style="color:red">xxx</span>'s notes?</div>,
  type: 'warning'
})
Copy code

In addition, only one component can be written in a .vue file. This may not be convenient in some scenarios. In many cases, when writing a page, you may actually need to split some small node fragments into For reuse in small components, these small components can actually be solved by writing simple function components. Usually, due to the limitations of SFC, we may be accustomed to writing everything in one file, but I have to say that we can try this method.

//Write multiple components in one file
const Input = (props) => <input {...props} />
export const Textarea = (props) => <input {...props} />
export const Password = (props) => <input type="password" {...props} />

export default Input
Copy code

For example, an Input component is encapsulated here. We hope to export the Password component and Textarea component at the same time to facilitate users to use them according to actual needs. These two components themselves use the Input component internally, but some props are customized. It is very convenient in JSX. It is basically enough to write a simple function component, and just declare props through interface. But if it is written using a template, it may be split into three files, and perhaps an entry file of index.js must be added to export the three components.

Since JSX is essentially JavaScript, it has the full programming capabilities of JavaScript. For another example, we need to use a piece of logic to reverse a set of DOM nodes. If we write it in the template, we will probably need to write two pieces of code.

Although this example may not be common, we have to admit that in some scenarios, JSX is still easier to write than templates.

Starting from Vue 2, template will be compiled into JavaScript’s render function before running.

4075157587035f7275ad329515bf044e.jpeg

image.png

Vue recommends using templates to create your HTML in most cases. However, in some scenarios, you need to use the render function, which is more flexible than template. These render functions are the legendary Virtual DOM during the runtime stage.

8c687fc6321d27381a36a6f3c14027aa.jpeg

image.png

Basic use of JSX in Vue2

Configuration

In Vue 2, JSX compilation relies on @vue/babel-preset-jsx and @vue/babel-helper-vue-jsx-merge-props Bag. The former package is responsible for compiling the syntax of JSX, and the latter package is used to introduce the runtime mergeProps function.

npm install @vue/babel-preset-jsx @vue/babel-helper-vue-jsx-merge-props
Copy code

And add configuration in babel.config.js:

module.exports = {
  presets: ['@vue/babel-preset-jsx'],
}
Copy code

Text interpolation

Text interpolation in template code uses double curly braces by default:

<h1>{<!-- -->{ msg }}</h1>
Copy code

In JSX you need to use single curly braces:

const name = 'Vue'
const element = <h1>Hello, { name }</h1>
Copy code

Like text interpolation in template syntax, any valid JS expression is supported within curly brackets, such as: 2 + 2, user.firstName, formatName(user )etc.

Conditional and loop rendering

In the template code, we use v-for to traverse the elements, and use v-if to determine whether to render the element. In JSX, for v-for code>, you can use for loop or array.map instead. For v-if, you can use if-else statement, ternary expression, etc. instead

Use if-else statements

const element = (name) => {
  if (name) {
    return <h1>Hello, { name }</h1>
  } else {
    return <h1>Hello, Stranger</h1>
  }
}
Copy code

Use ternary expressions

const element = icon ? <span class="icon"></span> : null;
Copy code

Using the map method of an array

const list = ['java', 'c++ ', 'javascript', 'c#', 'php']
return (
  <ul>
  {list.map(item => {
   return <li>{item}</li>
  })}
  </ul>
)
Copy code

Attribute binding

In the template code, generally use v-bind:prop="value" or :prop="value" to bind properties to the component. In JSX< You can no longer use the v-bind instruction in /code>, but bind it in the form of single curly braces:

const href = 'https://xxx.com'
const element = <a href={href}>xxx</a>
Copy code
const properties = {a: 1, b: 2}
Copy code

In addition, tag attributes can be bound in batches through

in the template code.

There is also a corresponding replacement solution in JSX:

.

Class binding also uses single curly brackets.

const element = <div className={`accordion-item-title ${ disabled ? 'disabled' : '' }`}></div>
const element = <div class={
    [ 'accordion-item-title', disabled & amp; & amp; 'disabled' ]
  }
>Item</div>
Copy code

Style binding requires the use of double braces

const width = '100px'
const element = <button style={<!-- -->{ width, fontSize: '16px' }}></button>
Copy code

Event binding

In the template code, the event is monitored through the v-on directive. In JSX, the event is monitored through on + the event name written in camel case, and the bound event is also used in curly brackets. For example, the click event is written as onClick, the mouseenter event should be written as onMouseenter

const confirm = () => {
  // confirm submission
}
<button onClick={confirm}>Confirm</button>
Copy code

Sometimes we hope to monitor native events on the root element of a component. In this case, the .native modifier will be used, but it cannot be used in JSX. However, there are alternatives to monitor native events. The rules are the same as ordinary events. You only need to replace the previous on with nativeOn, as follows

render() {
    //Listen to the click event of the root element of the drop-down box
    return <CustomSelect nativeOnClick={this.handleClick}></CustomSelect>
  }
Copy code

In addition to the above methods of monitoring events, we can also use objects to monitor events.

render() {
    return (
      <ElInput
        value={this.content}
        on={<!-- -->{
          focus: this.handleFocus,
          input: this.handleInput
        }}
        nativeOn={<!-- -->{
          click: this.handleClick
        }}
      ></ElInput>
    )
  }
Copy code

For the event modifiers .passive, .capture and .once, Vue provides corresponding prefixes that can be used for on code>:

b4ba4a7ea9942f01391ba0a3551654a4.jpeg

image.png

For example:

on: {
    '!click': this.doThisInCapturingMode,
    '~keyup': this.doThisOnce,
    '~!mouseover': this.doThisOnceInCapturingMode
}
Copy code

For all other modifiers, the private prefix is not necessary because you can use the event method in the event handler: .jpeg" alt="b4d5788bb489de7a79478278443df21e.jpeg"> For details, please refer to the Vue specification document.

v-show and v-model

Most directives cannot be used in JSX. For native directives, only v-show is supported.

And v-model is a syntax sugar provided by Vue. It is essentially composed of value attribute (default) + input event (default), so in JSX, we can return to the essence by passing the value attribute and listening to the input event To manually implement two-way binding of data:

export default {
  data() {
    return {
      name: ''
    }
  },
  methods: {
    // Listen to the onInput event for assignment operations
    handleInput(e) {
      this.name = e.target.value
    }
  },
  render() {
    // Pass the value attribute and listen to the onInput event
    return <input value={this.name} onInput={this.handleInput}></input>
  }
}
Copy code

In addition, in the scaffolding vue-cli4, support for v-model has been integrated by default, and you can directly use , if the project is relatively old, you can also install the plug-in babel-plugin-jsx-v-model for support.

Similarly, in JSX, .sync also needs to be implemented using attributes + events, as shown in the following code:

export default {
  methods: {
    handleChangeVisible(value) {
      this.visible = value
    }
  },
  render() {
    return (
      <ElDialog
        title="Test.sync"
        visible={this.visible}
        on={<!-- -->{ 'update:visible': this.handleChangeVisible }}
      ></ElDialog>
    )
  }
}
Copy code

Slot

(1)Default slot:

When using element-ui's Dialog, the pop-up content uses the default slot. The usage of using the default slot in JSX is the same as that of ordinary The usage of the slot is basically the same, as follows

render() {
    return (
      <ElDialog title="Pop title" visible={this.visible}>
        {/*This is the default slot*/}
        <div>Here is the pop-up content</div>
      </ElDialog>
    )
  }
Copy code

Customize the default slot:

There is an attribute $slots on the instance Vue of this, which mounts all the slots inside this component. Use this.$slots.default can add the default slot to the component.

export default {
  props: {
    visible: {
      type: Boolean,
      default: false
    }
  },
  render() {
    return (
      <div class="custom-dialog" vShow={this.visible}>
        {/**Define the default slot through this.$slots.default*/}
        {this.$slots.default}
      </div>
    )
  }
}
Copy code

(2) Named slot

Sometimes we need multiple slots for a component. At this time, we need to give each slot a name. For example, the pop-up box of element-ui can define the content of the bottom button area by using the name. Slot for footer

render() {
    return (
      <ElDialog title="Pop title" visible={this.visible}>
        <div>Here is the pop-up content</div>
        {/** Named slot */}
        <template slot="footer">
          <ElButton>OK</ElButton>
          <ElButton>Cancel</ElButton>
        </template>
      </ElDialog>
    )
  }
Copy code

Customize named slots: $slots was mentioned in the previous section when customizing default slots. For default slots, use this.$slots.default, and for named slots Slots can be customized using this.$slots.footer

render() {
    return (
      <div class="custom-dialog" vShow={this.visible}>
        {this.$slots.default}
        {/**Custom named slot*/}
        <div class="custom-dialog__foolter">{this.$slots.footer}</div>
      </div>
    )
  }
Copy code

(3) Scope slot

Sometimes it is useful to allow slot content to access data only in child components. In this case, scope slots are needed. In JSX, because there is no v-slot directive, so the way the scope slot is used is different from the way in the template code. For example, in element-ui, when we use el-table, we can customize the content of table cells. In this case, we need to use scope slots

data() {
    return {
      data: [
        {
          name: 'xxx'
        }
      ]
    }
  },
  render() {
    return (
      {/**scopedSlots is the scoped slot, default is the default slot, if it is a named slot, set default to the corresponding slot name*/}
      <ElTable data={this.data}>
        <ElTableColumn
          label="name"
          scopedSlots={<!-- -->{
            default: ({ row }) => {
              return <div style="color:red;">{row.name}</div>
            }
          }}
        ></ElTableColumn>
      </ElTable>
    )
  }
Copy code

Custom scope slot:

Using scope slots is different, and defining scope slots is also different from the template code. We have customized a list item component. The user wants to customize the list item title. At this time, the list data needs to be passed out through the scope slot.

render() {
    const { data } = this
    // Get the title scope slot
    const titleSlot = this.$scopedSlots.title
    return (
      <div class="item">
        {/** If there is a title slot, use the title slot, otherwise use the default title */}
        {titleSlot ? titleSlot(data) : <span>{data.title}</span>}
      </div>
    )
  }
Copy code

Use custom components

Just import it, no need to declare it in the components attribute, just write it directly in jsx:

import MyComponent from './my-component'

export default {
  render() {
    return <MyComponent>hello</MyComponent>
  },
}
Copy code

Return JSX in method

We can define method, then return JSX in method, and then call this method in the render function, not only In this way, JSX can also be directly assigned to variables, such as:

methods: {
    renderFooter() {
      return (
        <div>
          <ElButton>OK</ElButton>
          <ElButton>Cancel</ElButton>
        </div>
      )
    }
  },
  render() {
    const buttons = this.renderFooter()
    return (
      <ElDialog visible={this.visible}>
        <div>content</div>
        <template slot="footer">{buttons}</template>
      </ElDialog>
    )
  }
Copy code

Use JSX to implement simple chat records

Assume that the message chat record has only three message types: text, picture, and quote. A message can contain any type of content, and a reference type message can continuously reference other types of messages. The effect diagram is roughly as follows:

acd48c6fd2b3b6c8ec015dabbe8c0b18.jpeg

image.png

The message data structure is as follows:

message: [
      // The first parameter of each array is the message type: 0: text 1: image 2: reference. The second parameter is the specific content
      [
        0,
        'text'
      ],
      [
        1,
        'Picture link xxx'
      ],
      [
        2,
        [
          [
            0,
            'Quote text text text'
          ],
          [
            1,
            'Quote picture link xxx'
          ]
        ]
      ]
    ]
Copy code

There are two main ideas:

1. Idea 1: Return a message template rendered using array.map in render. For the three message types, use if-else to judge and render them separately. For reference type messages, you can encapsulate a method for rendering. In the method, if If there are still reference type messages, recursive rendering will continue.

methods: {
    // Display reference message
    showQuote (msg) {
      return (
        <div class="content-quote">
          <span class="quote-title">Quote:</span>
          {msg.map(item => {
            if (item[0] === 0) {
              return <p class="content-text">{item[1]}</p>
            } else if (item[0] === 1) {
              return (
                <el-image
                  class="content-img"
                  src={item[1]}
                  preview-src-list={[item[1]]}>
                </el-image>
              )
            } else {
              return this.showQuote(item[1])
            }
          })}
        </div>
      )
    }
  },
  render (h) {
    return (
      <ul class="chat-record-list">
        {this.recordList.map(item => {
          return (
            <li
              class="chat-record-item"
              key={item.timeStamp}
            >
              <div class="title">
                <span class="person-info">
                  { `${item.sendUserNick}(${item.sendUserNet}) → ${item.receiverNick}(${item.receiverNet})` }
                </span>
                <span class="sendtime">
                  { this.formatTime('YYYY-mm-dd HH:MM:SS', item.timeStamp) }
                </span>
              </div>
              <div class="content">
                {item.message.map(msg => {
                  if (msg[0] === 0) {
                    return <p class="content-text">{msg[1]}</p>
                  } else if (msg[0] === 1) {
                    return (
                      <el-image
                        class="content-img"
                        src={msg[1]}
                        preview-src-list={[msg[1]]}>
                      </el-image>
                    )
                  } else {
                    // Recursively render reference type messages
                    return this.showQuote(msg[1])
                  }
                })}
              </div>
            </li>
          )
        })}
      </ul>
    )
  }
Copy code

2. Idea 2: The code in showQuote encapsulated in the first idea is basically similar to the code for rendering message content in render, so its implementation is not elegant enough. In fact, you can encapsulate the rendering of the entire message into a component, introduce yourself into the component, and then render yourself. Since the specific details of the code are similar to the above, only the idea code is given here. Please ignore the specific details.

//The current component is the RecordMessage component, introduce yourself
import RecordMessage from './RecordMessage.vue'

export default {
    props: {
        message: {
            type: Array,
            default: () => []
        }
    },
    render () {
        const parseMessage = msg => {
            const type = msg[0]
            if (type === 0) {
                // text
            } else if (type === 2) {
                // picture
            } else {
                // reference type
                return (
                    <div>
                        <div>Quote:</div>
                        {
                            msg[1].map(subMsg => (
                                // Render yourself recursively
                                <recorded-message>
                                </recorded-message>
                            ))
                        }
                    </div>
                )
            }
        }
        return parseMessage(this.message)
    }
Copy code

About this article

Author: Rockky

https://juejin.cn/post/7188898676993949752

3f34479801edf7bb9784567a55f0aeaf.png

Recommended in the past

Solve the problem of messy import import order

25508510d6739e19fd3fe2a0fb317155.png

Restore the scene - front-end recording user behavior technical solution

c6d10483ec894ffec8eb7a42c3d74599.png

Sorry localStorage, now I'm in love with localForage!

1da9551bd8e19d09a4cc44d52f418fef.png

at last

  • Welcome to add me on WeChat and join the technical group for long-term communication and learning...

  • Welcome to pay attention to "Front-End Q", learn front-end seriously, and become a professional technical person...

106e97de1d1aed619ace0f691c3a6298.jpeg

ad4c3bdea950d9c32a3301fb6133ce7d.png

Click Watching to support me

b6ca18b405d7922d23de225173189438.gif

syntaxbug.com © 2021 All Rights Reserved.