zoukankan      html  css  js  c++  java
  • Vue相关,Vue JSX

    JSX简介

    JSX是一种Javascript的语法扩展,JSX = Javascript + XML,即在Javascript里面写XML,因为JSX的这个特性,所以他即具备了Javascript的灵活性,同时又兼具html的语义化和直观性。

     

    应用场景

    为了让大家更方便的去理解JSX的作用及用法,小编先为大家罗列了几个可能会用到JSX的应用场景。

     

    在消息框内添加html

    在开发过程中,经常会用到消息框,使用消息框可能的一种写法是这样的。

    Message.alert({
      messge: '确定要删除?',
      type: 'warning'
    })

    但是有时候产品或UI希望message可以自定义一些样式,这时候你可能就需要让Message.alert支持JSX了(当然也可以使用插槽/html等方式解决)

    Message.alert({
      // 此处使用了JSX
      messge: <div>确定要删除<span style="color:red">学习子君Vue系列文章</span>的笔记?</div>,
      type: 'warning'
    })

    函数式组件

    在小编前面的文章实战技巧,Vue原来还可以这样写中介绍了为什么要使用函数式组件,及函数式组件与普通组件的区别。

    虽然在Vue.2.5之后,函数式组件也可以使用模板语法,但使用JSX可能会更方便一些(个人理解)

    export default {
      // 通过配置functional属性指定组件为函数式组件
      functional: true,
      /**
       * 渲染函数
       * @param {*} h
       * @param {*} context 函数式组件没有this, props, slots等都在context上面挂着
       */
      render(h, context) {
        const { props } = context
        if (props.avatar) {
          return <img src={props.avatar}></img>
        }
        return <img src="default-avatar.png"></img>
      }
    }

    一个表单的需求

    为了方便快速开发管理系统,小编对所使用的UI库中的表单进行了二次封装,封装之后的效果如下(仅供参考):

    <template>
      <custom-form v-model="formData" :fields="fields" />
    </template>
    <script>
    export default {
      data() {
        return {
          formData: {},
          fields: Object.freeze([
            {
              label: '字段1',
              props: 'field1',
              type: 'input'
            },
            {
              label: '字段2',
              props: 'field2',
              type: 'number'
            }
          ])
        }
      }
    }
    </script>

    这样封装之后,定义表单时,只需要定义简单的JSON即可快速完成表单开发。

    但有时候会有一些特殊的需求,比如希望可以给输入框后面加一个按钮或者图标之类的,这时候就需要考虑使用JSX去处理了

    {
      label: '字段2',
      props: 'field2',
      type: 'number',
      // 会渲染到表单元素后面
      renderSuffix() {
        return <button onClick={this.$_handleSelect}>选择</button>
      }
    }

    其他一些场景

    比如我们一条数据需要根据状态不同,定义不同的展现方式,这时候你可能会想到用策略模式,这时候如果将每一个策略都写成一个JSX,那么就不需要针对每一个策略定义一个单文件组件了。

    当然如果你说,我就喜欢用JSX,那么所有的场景你都可以用。

     

    学习JSX,先了解一下createElement 

    你是否看过写的Vue代码经过编译之后的样子,比如下面这段代码

    <template>
      <div>我是子君,我的公众号是<span class="emphasize">前端有的玩</span></div>
    </template>

    小编对这段代码进行编译之后,得到下面这段代码

    function () {
      var e = this,
      // e._self._c 对应源码里面的createElement
      t = e._self._c;
      // 返回了一个 createElement('div',[])
      return t("div", [
        // e._v 对应源码里面的createTextVNode
        e._v("我是子君,我的公众号是"),
        t("span", { staticClass: "emphasize" }, [e._v("前端有的玩")]),
      ]);
    }

    通过对上面的代码进行分析,不难发现,Vue模板中的每一个元素编译之后都会对应一个createElement,那么这个createElement到底是什么,嗯,这个你面试的时候也许已经提到过了。

    那么什么是createElement

    无论是Vue还是React,都存在createElement,而且作用基本一致。

    可能你对createElement不是很了解,函数名翻译过来就是增加一个元素,但他的返回值你一定知道。

    createElement函数返回的值称之为虚拟节点,即VNode,而由VNode扎堆组成的树便是大名鼎鼎,面试必问的虚拟DOM

    createElement函数的参数,在这里小编偷个懒抄一下Vue官方文档

    // @returns {VNode}
    createElement(
      // {String | Object | Function}
      // 一个 HTML 标签名、组件选项对象,或者
      // resolve 了上述任何一种的一个 async 函数。必填项。
      'div',
    
      // {Object}
      // 一个与模板中 attribute 对应的数据对象。可选。
      {
        // (详情见下一节)
      },
    
      // {String | Array}
      // 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
      // 也可以使用字符串来生成“文本虚拟节点”。可选。
      [
        '先写一些文字',
        createElement('h1', '一则头条'),
        createElement(MyComponent, {
          props: {
            someProp: 'foobar'
          }
        })
      ]
    )

    从上面可以看出createElement一共有三个参数,三个参数分别是

    • 第一个参数是需要渲染的组件,可以是组件的标签,比如div;或者是一个组件对象,也就是你天天写的export default {};亦或者可以是一个异步函数。

    • 第二个参数是这个组件的属性,是一个对象,如果组件没有参数,可以传null(关于组件的属性,下文将依次介绍)

    • 第三个参数是这个组件的子组件,可以是一个字符串(textContent)或者一个由VNodes组成的数组

     

    createElement写一个组件吧

    表单示例

    假设我们需要开发一个下面这样的表格(element-ui的)

    用模板代码去开发

    如果我们用模板代码去开发这个表单,那么代码大概就长这样

    <el-form :inline="true" :model="formInline" class="demo-form-inline">
      <el-form-item label="审批人">
        <el-input v-model="formInline.user" placeholder="审批人"></el-input>
      </el-form-item>
      <el-form-item label="活动区域">
        <el-select v-model="formInline.region" placeholder="活动区域">
          <el-option label="区域一" value="shanghai"></el-option>
          <el-option label="区域二" value="beijing"></el-option>
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="onSubmit">查询</el-button>
      </el-form-item>
    </el-form>

    createElement去实现

    如果我们直接将上面的代码转换为用createElement去实现,那么代码将会是这样的

    export default {
     methods: {
        $_handleChangeUser(value) {
          this.formInline.user = value
        }
      },
      render(createElement) {
        return createElement(
          'ElForm',
          {
            props: {
              inline: true,
              model: this.formInline
            },
            staticClass: 'demo-form-inline'
          },
          [
            createElement(
              'ElFormItem',
              {
                props: {
                  label: '审批人'
                }
              },
              [
                createElement('ElInput', {
                  props: {
                    value: this.formInline.user
                  },
                  attrs: {
                    placeholder: '审批人'
                  },
                  on: {
                    input: this.$_handleChangeUser
                  }
                })
              ]
            ),
            createElement(
              'ElFormItem',
              {
                props: {
                  label: '活动区域'
                }
              },
              [
                createElement(
                  'ElSelect',
                  {
                    props: {
                      value: this.formInline.region,
                      placeholder: '活动区域'
                    }
                  },
                  [
                    createElement('ElOption', {
                      props: {
                        label: '区域一',
                        value: 'shanghai'
                      }
                    }),
                    createElement('ElOption', {
                      props: {
                        label: '区域二',
                        value: 'beijing'
                      }
                    })
                  ]
                )
              ]
            ),
            createElement('ElFormItem', null, [
              createElement(
                'ElButton',
                {
                  props: {
                    type: 'primary'
                  },
                  on: {
                    click: this.$_handleSubmit
                  }
                },
                '查询'
              )
            ])
          ]
        )
      }
    }

    看到上面的代码,你可能会惊呼,代码好多啊,好痛苦,想当年发明JSX的人刚开始天天也是写createElement,写的直掉头发,太痛苦了。

    然后就使劲挠头,当额头锃光发亮的时候,终于想到了一种新的语法,就是JSX。从此之后,头发呼呼的又长回来了(本段纯属虚构)。

    看到上面代码,你会发现有一个render函数,这个函数叫做渲染函数,相当于通过createElementJSX去实现功能的主入口方法。

    而且你熟悉的v-model也没见了,而是用value + input代替了。



    是时候使用JSX代替createElement

    看到上面用createElement去实现组件,太麻烦了,别说工作效率提高了,就是那些嵌套可以嵌套正确就很赞了,所以我们需要用JSX去简化整个逻辑。

    methods: {
      $_handleInputUser(value) {
        this.formInline.user = value
      },
      $_handleChangeRegion(value) {
        this.formInline.region = value
      },
      $_handleSubmit() {}
    },
      /**
      *将 h 作为 createElement 的别名是 Vue 生态系统中的一个通用惯例,实际上也是 JSX 所要求的。从 Vue 的 Babel 插件的 3.4.0   *版本开始,我们会在以 ES2015 语法声明的含有 JSX 的任何方法和 getter 中 (不是函数或箭头函数中) 自动注入 
      *const h = this.$createElement,这样你就可以去掉 (h) 参数了。对于更早版本的插件,如果 h 在当前作用域中不可用,应用会抛错。
     */
    render(h) {
        return (
          <ElForm inline model={this.formInline} class="demo-form-inline">
            <ElFormItem label="审批人">
              <ElInput
                value={this.formInline.user}
                onInput={this.$_handleInputUser}
                placeholder="审批人"
              ></ElInput>
            </ElFormItem>
            <ElFormItem label="活动区域">
              <ElSelect
                value={this.formInline.region}
                onChange={this.$_handleChangeRegion}
                placeholder="活动区域"
              >
                <ElOption label="区域一" value="shanghai"></ElOption>
                <ElOption label="区域二" value="beijing"></ElOption>
              </ElSelect>
            </ElFormItem>
            <ElFormItem>
              <ElButton type="primarty" onClick={this.$_handleSubmit}>
                查询
              </ElButton>
            </ElFormItem>
          </ElForm>
        )
      }

    看了上面的代码,大家其实会发现用JSXtemplate的语法都属于xml的写法,而且也比较像,但实质上还是有许多区别的,下面小编将为大家一一分析

    没有v-model怎么办,还有其他指令可以用吗?

    当你选择使用JSX的时候,你就要做好和指令说拜拜的时候了,在JSX中, 你唯一可以使用的指令是v-show,除此之外,其他指令都是不可以使用的,有没有感到很慌,这就对了。不过呢,换一个角度思考,指令只是Vue在模板代码里面提供的语法糖,现在你已经可以写Js了,那些语法糖用Js都可以代替了。

    v-model

    v-modelVue提供的一个语法糖,它本质上是由 value属性(默认) + input事件(默认)组成的,如果对自定义v-model不了解的同学建议阅读小编的文章进行了解 绝对干货~!学会这些Vue小技巧,可以早点下班和女神约会了。 所以,在JSX中,我们便可以回归本质,通过传递value属性并监听input事件来实现数据的双向绑定

    export default {
      data() {
        return {
          name: ''
        }
      },
      methods: {
        // 监听 onInput 事件进行赋值操作
        $_handleInput(e) {
          this.name = e.target.value
        }
      },
      render() {
        // 传递 value 属性 并监听 onInput事件
        return <input value={this.name} onInput={this.$_handleInput}></input>
      }
    }
    经小编测试,在新版脚手架vue-cli4中,已经默认集成了对v-model的支持,大家可以直接使用<input v-model={this.value}>,
    如果你的项目比较老,也可以安装插件babel-plugin-jsx-v-model来进行支持

    同样的,在JSX中,对于.sync也需要用属性+事件来实现,如下代码所示:

    export default {
      methods: {
        $_handleChangeVisible(value) {
          this.visible = value
        }
      },
      render() {
        return (
          <ElDialog
            title="测试.sync"
            visible={this.visible}
            on={{ 'update:visible': this.$_handleChangeVisible }}
          ></ElDialog>
        )
      }
    }

    v-if 与 v-for

    在模板代码里面我们通过v-for去遍历元素,通过v-if去判断是否渲染元素,在jsx中,对于v-for,你可以使用for循环,array.map来代替,

    对于v-if,可以使用if语句,三元表达式等来代替循环遍历列表

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

    使用条件判断

    const isGirl = false
    return isGirl ? <span>小妹,哥哥教你写Vue</span> : <span>鸟你干啥</span>

    v-bind

    在模板代码中,我们一般通过 v-bind:prop="value":prop="value"来给组件绑定属性,在JSX里面写法也类似

    render() {
      return <input value={this.name}></input>
    }

    v-html 与 v-text

    在说v-htmlv-text之前,我们需要先了解一下Vue中的属性,Vue中的属性一共分为三种,第一种是大家写bug时候最常用的props,即组件自定义的属性;第二种是attrs,是指在父作用域里面传入的,但并未在子组件内定义的属性。第三种比较特殊,是domProps,经小编不完全测试,在Vue中,domProps主要包含三个,分别是innerHTML,textContent/innerTextvalue

    • v-html: 在模板代码中,我们用v-html指令来更新元素的innerHTML内容,而在JSX里面,如果要操纵组件的innerHTML,就需要用到domProps

    export default {
      data() {
        return {
          content: '<div>这是子君写的一篇新的文章</div>'
        }
      },
      render() {
        // v-html 指令在JSX的写法是 domPropsInnerHTML
        return <div domPropsInnerHTML={this.content}></div>
      }
    }

    v-text: 看了上面的v-html,你是不是立即就想到了v-textJSX的写法domPropsInnerText,是的,你没有想错

    export default {
      data() {
        return {
          content: '这是子君写的一篇新的文章的内容'
        }
      },
      render() {
        return <div domPropsInnerText={this.content}></div>
      }
    }

    但实际上我们不需要使用domPropsInnerText,而是将文本作为元素的子节点去使用即可

    <div>{this.content}</div>

    实际上,对于domProps,只有innerHTML才需要使用domPropsInnerHTML的写法,其他使用正常写法即可

     

    我还要监听事件呢

    监听事件与原生事件

    当我们开发一个组件之后,一般会通过this.$emit('change')的方式对外暴露事件,然后通过v-on:change的方式去监听事件,

    很遗憾,在JSX中你无法使用v-on指令,但你将解锁一个新的姿势

     render() {
        return <CustomSelect onChange={this.$_handleChange}></CustomSelect>
      }

    JSX中,通过on + 事件名称的大驼峰写法来监听,比如事件icon-click,在JSX中写为onIconClick

    有时候我们希望可以监听一个组件根元素上面的原生事件,这时候会用到.native修饰符,有点绝望啊,修饰符也是不能用了,但好在也有替代方案,如下代码

     render() {
        // 监听下拉框根元素的click事件
        return <CustomSelect nativeOnClick={this.$_handleClick}></CustomSelect>
      }

    监听原生事件的规则与普通事件是一样的,只需要将前面的on替换为nativeOn

    除了上面的监听事件的方式之外,我们还可以使用对象的方式去监听事件

      render() {
        return (
          <ElInput
            value={this.content}
            on={{
              focus: this.$_handleFocus,
              input: this.$_handleInput
            }}
            nativeOn={{
              click: this.$_handleClick
            }}
          ></ElInput>
        )
      }

    事件修饰符

    和指令一样,除了个别的之外,大部分的事件修饰符都无法在JSX中使用,这时候你肯定已经习惯了,肯定有替代方案的。

    • .stop : 阻止事件冒泡,在JSX中使用event.stopPropagation()来代替

    • .prevent:阻止默认行为,在JSX中使用event.preventDefault() 来代替

    • .self:只当事件是从侦听器绑定的元素本身触发时才触发回调,使用下面的条件判断进行代替

    if (event.target !== event.currentTarget){
      return
    }

    .enterkeyCode: 在特定键触发时才触发回调

    if(event.keyCode === 13) {
      // 执行逻辑
    }

    除了上面这些修饰符之外,尤大大为了照顾我们这群CV仔,还是做了一点优化的,对于.once,.capture,.passive,.capture.once,尤大大提供了前缀语法帮助我们简化代码

     render() {
        return (
          <div
            on={{
              // 相当于 :click.capture
              '!click': this.$_handleClick,
              // 相当于 :input.once
              '~input': this.$_handleInput,
              // 相当于 :mousedown.passive
              '&mousedown': this.$_handleMouseDown,
              // 相当于 :mouseup.capture.once
              '~!mouseup': this.$_handleMouseUp
            }}
          ></div>
        )
      }

    对了,还有插槽

    插槽就是子组件中提供给父组件使用的一个占位符,插槽分为默认插槽,具名插槽和作用域插槽,下面小编依次为你带来每种在JSX中的用法与如何去定义插槽。

    默认插槽

    • 使用默认插槽

    使用element-uiDialog时,弹框内容就使用了默认插槽,在JSX中使用默认插槽的用法与普通插槽的用法基本是一致的,如下代码所示:

     render() {
        return (
          <ElDialog title="弹框标题" visible={this.visible}>
            {/*这里就是默认插槽*/}
            <div>这里是弹框内容</div>
          </ElDialog>
        )
      }
    • 自定义默认插槽

    Vue的实例this上面有一个属性$slots,这个上面就挂载了一个这个组件内部的所有插槽,使用this.$slots.default就可以将默认插槽加入到组件内部

    export default {
      props: {
        visible: {
          type: Boolean,
          default: false
        }
      },
      render() {
        return (
          <div class="custom-dialog" vShow={this.visible}>
            {/**通过this.$slots.default定义默认插槽*/}
            {this.$slots.default}
          </div>
        )
      }
    }

    具名插槽

    • 使用具名插槽

      有时候我们一个组件需要多个插槽,这时候就需要为每一个插槽起一个名字,比如element-ui的弹框可以定义底部按钮区的内容,就是用了名字为footer的插槽

     render() {
        return (
          <ElDialog title="弹框标题" visible={this.visible}>
            <div>这里是弹框内容</div>
            {/** 具名插槽 */}
            <template slot="footer">
              <ElButton>确定</ElButton>
              <ElButton>取消</ElButton>
            </template>
          </ElDialog>
        )
      }
    • 自定义具名插槽

    在上节自定义默认插槽时提到了$slots,对于默认插槽使用this.$slots.default,而对于具名插槽,可以使用this.$slots.footer进行自定义

    render() {
        return (
          <div class="custom-dialog" vShow={this.visible}>
            {this.$slots.default}
            {/**自定义具名插槽*/}
            <div class="custom-dialog__foolter">{this.$slots.footer}</div>
          </div>
        )
      }

    作用域插槽

    • 使用作用域插槽

      有时让插槽内容能够访问子组件中才有的数据是很有用的,这时候就需要用到作用域插槽,在JSX中,因为没有v-slot指令,所以作用域插槽的使用方式就与模板代码里面的方式有所不同了。比如在element-ui中,我们使用el-table的时候可以自定义表格单元格的内容,这时候就需要用到作用域插槽

    data() {
        return {
          data: [
            {
              name: '子君'
            }
          ]
        }
      },
      render() {
        return (
          {/**scopedSlots即作用域插槽,default为默认插槽,如果是具名插槽,将default该为对应插槽名称即可*/}
          <ElTable data={this.data}>
            <ElTableColumn
              label="姓名"
              scopedSlots={{
                default: ({ row }) => {
                  return <div style="color:red;">{row.name}</div>
                }
              }}
            ></ElTableColumn>
          </ElTable>
        )
      }

    自定义作用域插槽

    使用作用域插槽不同,定义作用域插槽也与模板代码里面有所不同。加入我们自定义了一个列表项组件,用户希望可以自定义列表项标题,这时候就需要将列表的数据通过作用域插槽传出来。

    render() {
        const { data } = this
        // 获取标题作用域插槽
        const titleSlot = this.$scopedSlots.title
        return (
          <div class="item">
            {/** 如果有标题插槽,则使用标题插槽,否则使用默认标题 */}
            {titleSlot ? titleSlot(data) : <span>{data.title}</span>}
          </div>
        )
      }

    只能在render函数里面使用JSX

    当然不是,你可以定义method,然后在method里面返回JSX,然后在render函数里面调用这个方法,不仅如此,JSX还可以直接赋值给变量,比如下面这段代码

     methods: {
        $_renderFooter() {
          return (
            <div>
              <ElButton>确定</ElButton>
              <ElButton>取消</ElButton>
            </div>
          )
        }
      },
      render() {
        const buttons = this.$_renderFooter()
        return (
          <ElDialog visible={this.visible}>
            <div>这里是一大坨内容</div>
            <template slot="footer">{buttons}</template>
          </ElDialog>
        )
      }

     

    指令

    基础用法

    虽然大部分内置的指令无法直接在JSX里面使用,但是自定义的指令可以在JSX里面使用,就拿element-uiv-loading指令来说,可以这样用

      render() {
        /**
         * 一个组件上面可以使用多个指令,所以是一个数组
         * name 对应指令的名称, 需要去掉 v- 前缀
         * value 对应 `v-loading="value"`中的value
         */
        const directives = [{ name: 'loading', value: this.loading }]
        return (
          <div
            {...{
              directives
            }}
          ></div>
        )
      }

    修饰符

    有些指令还可以使用修饰符,比如上例中的v-loading,你可以通过修饰符指定是否全屏遮罩,是否锁定屏幕的滚动,这时候就需要这样写 v-loading.fullscreen.lock = "loading"

      render() {
        /**
         * modifiers指定修饰符,如果使用某一个修饰符,则指定这个修饰符的值为 true
         * 不使用可以设置为false或者直接删掉
         */
        const directives = [
          {
            name: 'loading',
            value: this.loading,
            modifiers: { fullscreen: true, lock: false }
          }
        ]
        return (
          <div
            {...{
              directives
            }}
          ></div>
        )
      }
  • 相关阅读:
    CodeForces 785D Anton and School
    CodeForces 785C Anton and Fairy Tale
    CodeForces 785B Anton and Classes
    CodeForces 785A Anton and Polyhedrons
    爱奇艺全国高校算法大赛初赛C
    爱奇艺全国高校算法大赛初赛B
    爱奇艺全国高校算法大赛初赛A
    EOJ 3265 七巧板
    EOJ 3256 拼音魔法
    EOJ 3262 黑心啤酒厂
  • 原文地址:https://www.cnblogs.com/magicg/p/13265263.html
Copyright © 2011-2022 走看看