Wang's blog Wang's blog
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Wang Mings

跟随大神,成为大神!
首页
  • 前端文章

    • HTML教程
    • CSS
    • JavaScript
  • 前端框架

    • Vue
    • React
    • VuePress
    • Electron
  • 后端技术

    • Npm
    • Node
    • TypeScript
  • 编程规范

    • 规范
  • 我的笔记
  • Git
  • GitHub
  • VSCode
  • Mac工具
  • 数据库
  • Google
  • 服务器
  • Python爬虫
  • 前端教程
更多
收藏
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • Python爬虫

  • 前端教程

    • 团队规范

    • Project

      • Axios用法与原理
      • css布局技巧
      • 深度学习平台术语
      • 谈谈前端天花板问题
      • 一个程序员的成长之路
      • Markdown-It 解析原理
      • minipack源码解析
      • PostCSS
      • Electron工程踩坑记录
      • H5 Video踩坑记录
      • Puppeteer翻页爬虫
      • 重构你的javascript代码
      • RxJS入门实践
      • 官网脚手架思考与实践
      • Stylelint样式规范工具
      • TypeScript开发Vue应用
      • Typescript tsconfig.json全解析
      • Vue项目TypeScript指南
      • TypeScript在Vue2.x中的坑
      • Vue Dialog弹窗解决方案
      • Vue JSX插件依赖及语法实践
        • [#](#_1-环境基础babel) 1. 环境基础babel
          • [#](#_1-1-babel-jsx插件集合) 1.1 Babel JSX插件集合
          • [#](#_1-2-babel预设插件集合) 1.2 Babel预设插件集合
          • [#](#_1-3-其他) 1.3 其他
        • [#](#_2-jsx在vue中语法) 2. JSX在Vue中语法
          • [#](#_2-1-基础语法) 2.1 基础语法
          • [#](#_2-2-动态属性) 2.2 动态属性
          • [#](#_2-3-指令) 2.3 指令
          • [#](#_2-4-slot插槽) 2.4 slot插槽
          • [#](#_2-5-组件) 2.5 组件
          • [#](#_2-6-functional函数) 2.6 functional函数
          • [#](#_2-7-v-model) 2.7 v-model
        • [#](#参考文章) 参考文章
      • Webpack 模块打包原理
      • Webpack4 配置详解
      • Webpack4 devServer配置详解
      • Webpack3.x升级Webpack4指南
    • JS

    • NodeJS

    • Vue

    • React

    • 效率工具

    • 读书笔记

  • 教程
  • 前端教程
  • Project
wangmings
2022-07-19
目录

Vue JSX插件依赖及语法实践

# # Vue JSX插件依赖及语法实践

文章内容主要分两块。

第一块是了解jsx运行环境,因为jsx只是语法糖,最终都是需要babel来转译语法,所以需要配置相关babel插件。vue-cli3脚手架工具生成的应用工程默认支持jsx/tsx,省去了自己配置的繁琐,但了解相关babel插件对理解和书写jsx非常有帮助。

第二块是实践jsx在vue中的语法以及相关案例。了解jsx是如何生成最终的VNode。tsx应用Demo代码放在github vue-tsx-demo (opens new window) (opens new window)

# # 1. 环境基础babel

vue-cli3自动生成的app项目中,babel.config.js预设了 presets: ["@vue/app"],该插件为babel-preset-app (opens new window) (opens new window)

里面包含插件,主要是babel解析,以支持许多扩展语法。比如jsx、es6语法等:

    "@babel/core": "^7.9.0",
    "@babel/helper-compilation-targets": "^7.8.7",
    "@babel/helper-module-imports": "^7.8.3",
    "@babel/plugin-proposal-class-properties": "^7.8.3",
    "@babel/plugin-proposal-decorators": "^7.8.3",
    "@babel/plugin-syntax-dynamic-import": "^7.8.3",
    "@babel/plugin-syntax-jsx": "^7.8.3",
    "@babel/plugin-transform-runtime": "^7.9.0",
    "@babel/preset-env": "^7.9.0",
    "@babel/runtime": "^7.9.2",
    "@vue/babel-preset-jsx": "^1.1.2",
    "babel-plugin-dynamic-import-node": "^2.3.0",
    
1
2
3
4
5
6
7
8
9
10
11
12
13

里面重点插件有:

# # 1.1 Babel JSX插件集合

  • @vue/babel-preset-jsx (opens new window) (opens new window):vue jsx插件集合,vue官方出品
    • 解析jsx为vnode函数(核心)@vue/babel-plugin-transform-vue-jsx (opens new window) (opens new window)
      • 支持解析jsx
      • 支持jsx props扩展
      • 支持vue directives
      • 支持直接导入component
    • v-model语法糖 @vue/babel-sugar-v-model (opens new window) (opens new window)
    • 自动在render函数注入h方法 @vue/babel-sugar-inject-h (opens new window) (opens new window)
    • 支持在jsx中 functional @vue/babel-sugar-functional-vue (opens new window) (opens new window)
    • 支持v-on事件 @vue/babel-sugar-v-on (opens new window) (opens new window)
    • 自动合并jsx props @vue/babel-helper-vue-jsx-merge-props (opens new window) (opens new window)

# # 1.2 Babel预设插件集合

  • @babel/preset-env (opens new window) (opens new window) babel智能预设
    • 集成了browserslist (opens new window) (opens new window),该插件包含其他核心插件:
      • postcss-preset-env (opens new window) (opens new window),将现代css转换成浏览器理解的
      • autoprefixer (opens new window) (opens new window),解析css成兼容多个浏览器
    • 众多ES6 Stage语法支持,比如:
      • 支持class语法 (opens new window) (opens new window)
      • 支持对象rest spread (opens new window) (opens new window)
      • 支持for of (opens new window) (opens new window)
      • 支持箭头函数 (opens new window) (opens new window)
    • 参数targets:为项目支持/目标的环境
      • 默认转换所有ECMAScript 2015+代码
      • chrome,opera,edge,firefox,safari,ie,ios,android 确定最低版本要求
      • node
      • electron
    • 参数targets.esmodules: 定位为支持ES模块的浏览器(直接支持ES6模块语法,浏览器能自己解析import语法,可显著减少包体积)。注意:指定esmodules目标时,浏览器目标将被忽略
    • 参数modules:启用将ES6模块语法转换为其他模块类型的功能。"amd" | "umd" | "systemjs" | "commonjs" | "cjs" | "auto" | false,默认为"auto"。设置为false不会转换模块
    • 参数targets.browsers: 同上targets作用
    • 参数target.node: 同上targets作用

# # 1.3 其他

  • @babel/core (opens new window) (opens new window): 根据本地配置的config文件 (opens new window) (opens new window)(babel7考虑到monorepos项目存在,所以采用了babel.config.json配置文件,以作用全局),把代码转换。(所有babel插件必备前置包)
  • @babel/cli (opens new window) (opens new window): 客户端执行,依赖上面@babel/core。

# # 2. JSX在Vue中语法

react和vue底层vnode diff对比不是使用相同的数据结构,所以导致两者jsx书写方式有些许不同。目前两者大部分jsx语法一致,是因为有各种babel插件辅助做了这部分事。但对于动态属性这样自由化较高的地方,需要我们知道两者本质区别(即不同的VNode数据结构)。

vue template模板本质上最终生成render函数,而render函数本质上是生成VNode,所以有必要了解这个VNode数据结构。Vue2.x VNode diff核心算法借鉴的是snabbdom (opens new window) (opens new window)库,所以数据结构也有snabbdom数据结构的影子,比如事件需要放置在on属性上,方便最终patch时挂载到真实dom元素上(react则自定义模拟事件系统,所有事件都冒泡到顶层document处理)。更多VNode信息可查看Vue官方文档 - 深入数据对象 (opens new window) (opens new window)。

以下查看jsx转译为h函数:

    render (h) {
      return (
        <div
          // Component props
          propsMsg="hi"
          // Normal attributes or component props.
          id="foo"
          // DOM properties are prefixed with `domProps`
          domPropsInnerHTML="bar"
          // event listeners are prefixed with `on` or `nativeOn`
          onClick={this.clickHandler}
          nativeOnClick={this.nativeClickHandler}
          // other special top-level properties
          class={{ foo: true, bar: false }}
          style={{ color: 'red', fontSize: '14px' }}
          key="key"
          ref="ref"
          // assign the `ref` is used on elements/components with v-for
          refInFor
          slot="slot">
        </div>
      )
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

以上jsx语法糖等同于如何h函数生成VNode:

    render (h) {
      return h('div', {
        // Component props
        props: {
          msg: 'hi'
        },
        // Normal HTML attributes
        attrs: {
          id: 'foo'
        },
        // DOM props
        domProps: {
          innerHTML: 'bar'
        },
        // Event handlers are nested under "on", though
        // modifiers such as in v-on:keyup.enter are not
        // supported. You'll have to manually check the
        // keyCode in the handler instead.
        on: {
          click: this.clickHandler
        },
        // For components only. Allows you to listen to
        // native events, rather than events emitted from
        // the component using vm.$emit.
        nativeOn: {
          click: this.nativeClickHandler
        },
        // Class is a special module, same API as `v-bind:class`
        class: {
          foo: true,
          bar: false
        },
        // Style is also same as `v-bind:style`
        style: {
          color: 'red',
          fontSize: '14px'
        },
        // Other special top-level properties
        key: 'key',
        ref: 'ref',
        // Assign the `ref` is used on elements/components with v-for
        refInFor: true,
        slot: 'slot'
      })
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

使用jsx代替vue需要解决的系列问题:

  1. 与vue兼容,能被识别
  2. 支持使用v-model v-on(babel插件解决,见以上)
  3. 支持使用vue watch/computed/methods
  4. 支持使用created/mounted等生命周期
  5. 支持css module
  6. 支持动态props传值问题
  7. 支持插槽
  8. 支持Vue原型链上问题,如$xxx、ref等功能。

# # 2.1 基础语法

bable jsx插件会通过正则匹配的方式在编译阶段将书写在组件上属性进行“分类”。 onXXX的均被认为是事件,nativeOnXXX是原生事件,domPropsXXX是Dom属性。

class,staticClass,style,key,ref,refInFor,slot,scopedSlots这些被认为是顶级属性,至于我们属性声明的props,以及html属性attrs,不需要加前缀,插件会将其统一分类到attrs属性下,然后在运行阶段根据是否在props声明来决定属性归属(即属于props还是attrs)。

    export default {
      name: "button-counter",
      props: ["count"],
      methods: {
        onClick() {
          this.$emit("change", this.count + 1);
        }
      },
      render() {
        return (
          <button onClick={this.onClick}>You clicked me {this.count} times.</button>
        );
      }
    };
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# # 2.2 动态属性

在React中所有属性都是顶级属性,直接使用{...props}就可以了,但是在Vue中,你需要明确该属性所属的分类,如一个动态属性value和事件change,你可以使用如下方式(延展属性)传递:

    const dynamicProps = {
      props: {},
      on: {},
    }
    if(haValue) dynamicProps.props.value = value
    if(hasChange) dynamicProps.on.change = onChange
    <Dynamic {...dynamicProps} />
    
1
2
3
4
5
6
7
8

尽量使用明确分类的方式传递属性,而不是要babel插件帮你分类及合并属性。

# # 2.3 指令

常用的v-if和v-for,使用js语法中的if/for语句就能实现了。v-model属于prop + input事件语法糖,也可以使用babel插件自动实现。

    // v-show,同理v-if
    render(){
           return (
             <div>
               {this.show?'你帅':'你丑'}
             </div>
           )
         }
    
    // v-for
    render(){
            return (
              <div>
                {this.list.map((v)=>{
                  return <p>{v}</p>
                })}
              </div>
            )
          }
    
    // v-model: 传值和监听事件改变值(babel插件已支持)
    data(){
            return{
              text:'',
            }
          },
          methods:{
            input(e){
              this.text=e.target.value
            }
          },
          render(){
            return (
              <div>
                <input type="text" value={this.text} onInput={this.input}/>
                <p>{this.text}</p>
              </div>
            )
          }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# # 2.4 slot插槽

slot直接通过this.$slots对象拿到,scopedSlot通过this.$scopedSlots对象拿到($scopedSlots每项是待调用函数)。

    export default {
      render(h) {
        return <div>
            {
              (this.title || this.$slots.header) && (
                <div class="header">
                  <span class="title">{this.title}</span>
                  {this.$slots.header}
                </div>
              )
            }
            {this.$slots.default}
          </div>
      },
    }
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# # 2.5 组件

不需要注册,直接使用

    import Todo from './Todo.jsx'
    
    export default {
      render(h) {
        return <Todo /> // no need to register Todo via components option
      },
    }
    
1
2
3
4
5
6
7
8

# # 2.6 functional函数

    export default {
        functional:true,
        render(h,context){
            return (
              <div class="red" {...context.data}>
                {context.props.data}
              </div>
            )
          }
        }
    
1
2
3
4
5
6
7
8
9
10
11

# # 2.7 v-model

安装@vue/babel-sugar-v-model babel插件后即可自动解析v-model,官方更推荐使用vModel或者value + onInput事件。

    <el-input vModel_trim={inputValue}/>
    // 或者使用
    <el-input 
     value={this.inputValue}
     onInput={val => this.inputValue = val.trim()}/>
    
1
2
3
4
5
6

# # 参考文章

  • JavaScript modules 模块 (opens new window) (opens new window)
  • babel-preset-env (opens new window) (opens new window)
  • babel jsx语法扩展说明 (opens new window) (opens new window)
  • https://cn.vuejs.org/v2/guide/render-function.html#v-model
  • https://juejin.im/post/5b221e2951882574aa5f4c5a
  • https://juejin.im/post/5affa64df265da0b93488fdd
编辑 (opens new window)
Vue Dialog弹窗解决方案
Webpack 模块打包原理

← Vue Dialog弹窗解决方案 Webpack 模块打包原理→

最近更新
01
theme-vdoing-blog博客静态编译问题
09-16
02
搜索引擎
07-19
03
友情链接
07-19
更多文章>
Theme by Vdoing | Copyright © 2019-2022 Evan Xu | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式