背景

  学习前端的同学们应该都知道React和Vue在国内属于最热门的框架或者库。两者之间有许多相似,比如它们都有:

  • 使用虚拟DOM(Virtual DOM)
  • 提供了响应式 (Reactive) 和组件化 (Composable) 的视图组件
  • 将注意力集中保持在核心库,而将其他功能如路由和全局状态管理等交给相关的库

  但是又不一样,Vue是一个渐进式的Javascript框架,特点是易用,灵活,高效。React是一个用于构建用户界面的JavaScript库,特点是以声明式编写 UI,组件化,一次学习,随处编写。当然这里不是来说两者的区别的,只是为来引出在开发过程中比较直观的不一样那就是视图的编写方式。


JSX在React与Vue中的正确姿势

  React的项目中,一切都是JavaScript。HTML可以用JSX来编写,但是实际上JSX与HTML毛关系也没有,JSX实际是一种语法糖,最终还是会转换成JavaScript代码。但是JSX带来的优势却很明显,比如:

  • 你可以使用JavaScript来构建你的视图页面。

     //组件保存的变量名首字母必须大写,否则无法渲染成功
     // React DOM使用camelCase(小驼峰)命名来定义属性的名称,而不是使用HTML的属性名称
     //使用临时变量、JS自带的流程控制、以及直接引用当前 JS 作用域中的值等。
     const demoReact.createClass({
       render: function() {
         var dataArr = [1,2,3,4],
             jsxDomList = [];
          //临时变量 
         dataArr.forEach(function(item, index) {
            //js控制
            if(index < 4){
               //class 需要改为 className
               //大括号之之间只能写表达式(逻辑运算符(||、&&等)以及三目运算符而不能写判断等语句
              jsxDomList.push(<li className="item">{item}</li>);
            }
         });
         //事件绑定 内嵌样式
         return (<ul 
             onClick={this.handlerClick} 
           style={{width:30,height:30,backgroundColor:'red'}>
               {jsxDomList}
           </ul>);
       } 
       handlerClick: function() {
           alert('点了一下');
       }
    });
    ReactDOM.render(<HelloMessage name="John" />,document.getElementById('example'));
  • 开发工具对JSX的支持比现有可用的其他Vue模板更先进一些 (比如linting、类型检查、编辑器的自动完成)(强大生态的支持)

  Vue推荐的方式是使用template来创建我们的HTML,我相信日常开发中99%的前端用的都是这种方式。事实上,vue也提供了渲染函数,支持JSX语法。但是语法却不同。

Vue 选项中的 render 函数若存在,则 Vue 构造函数不会从 template 选项或通过 el 选项指定的挂载元素中提取出的 HTML 模板编译渲染函数。

  React中父子之间传递的所有数据都是属性,即所有数据均挂载在props下(style, className, children, value, onChange等等)。Vue则是属性就有三种:组件属性props,普通html属性attrs,Dom属性domProps。

  • React
var reactElement = ReactElement.createElement(
      'demo', // 标签名称字符串/ReactClass
      {
      id: "title",
      className: "bg",
      style: {
        color: 'red'
      }
    } // {元素的属性值对对象}或者是null
      sth... // [元素的子节点] 子节点数组 也可不传
)
  • Vue2.0
return createElement('div', {
  // 与 `v-bind:class` 的 API 相同
  'class': {
    foo: true,
  },

  // 与 `v-bind:style` 的 API 相同,
  // 接受一个字符串、对象,或对象组成的数组
  style: {
    color: 'blue',
    fontSize: '14px'
  },

  // 普通的 HTML 特性
  attrs: {
    id: 'foo'
  },

  // 组件 prop
  props: {
    myProp: 'bar'
  },
  // DOM 属性
  domProps: {
    innerHTML: 'eg'
  },
  // 事件监听器在 `on` 属性内,
  // 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
  // 需要在处理函数中手动检查 keyCode。
  on: {
    click: this.clickHandler
  },

  // 仅用于组件,用于监听原生事件,而不是组件内部使用
  // `vm.$emit` 触发的事件。
  nativeOn: {
    click: this.nativeClickHandler
  },

  // 自定义指令。注意,你无法对 `binding` 中的 `oldValue`
  // 赋值,因为 Vue 已经自动为你进行了同步
  // 比如常见的权限管理指令
  directives: [
    {
      name: 'permissions',
      value: {
        currentPermissions:
          'download_csv'
      }
    }
  ],

  // 作用域插槽的格式为
  // { name: props => VNode | Array<VNode> }
  scopedSlots: {
    default: props => createElement('span', props.text)
  },

  // 如果组件是其它组件的子组件,需为插槽指定名称
  slot: 'name-of-slot',
  // 其它特殊顶层属性
  key: 'myKey',
  ref: 'myRef',
  // 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
  // 那么 `$refs.myRef` 会变成一个数组。
  refInFor: true
})
  • Vue3.0
//Vue3.0 官方例子
const app = Vue.createApp({})
app.component('anchored-heading', {
  render() {
    const { h } = Vue
    return h(
      'h' + this.level, //标签名称字符串
      {}, // props/attributes
      this.$slots.default() //子节点数组 也可不传
    )
  },
  props: {
    level: {
      type: Number,
      required: true
    }
  }
})
//main.js
app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})
//component
setup(props, { slots, emit }) {
    //响应值
    const count = ref(1)
    //使用指令
    const vDemo = resolveDirective('demo')
    return ()=>{
      const dom = h('a',{
        class: ['wrap'],
        style: {
          zIndex: count.value,
        },
        onClick: ()=>{
          console.log('点了一下1');
        },
        onMouseover: ()=>{
          console.log('Mouseover了一下');
        },
        onUpdate:()=>{
          console.log('update');
        },
        // 正常的 HTML 特性
        href:'www.baidu.com',
        key: 'myKey',
        ref: 'myRef'
      },[
        h('span',null,123)
      ])
      console.log(dom);
      return withDirectives(dom,[[vDemo, { color: 'white', text: 'hello!' }]])
    }
  },

控制台打印

react的createElement源码

function createElement(type, config, children) {       
      var propName;
      //提取保留名称
      var props = {};
      var key = null;
      var ref = null;
      var self = null;
      var source = null;
      //标签的属性不为空时 说明标签有属性值 特殊处理:把key和ref赋值给单独的变量
      if (config != null) {
        //有合理的ref
        if (hasValidRef(config)) {
          ref = config.ref;
        }
        //有合理的key 
        if (hasValidKey(config)) {
          key = '' + config.key;
        }

        self = config.__self === undefined ? null : config.__self;
        source = config.__source === undefined ? null : config.__source;
        //config中剩余属性,且不是原生属性(RESERVED_PROPS对象的属性),则添加到新props对象中
        for (propName in config) {
          if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
            //config去除key/ref 其他属性的放到props对象中
            props[propName] = config[propName]; 
          }
        }
      }
      //子元素数量(第三个参数以及之后参数都是子元素 兄弟节点)
      var childrenLength = arguments.length - 2; 
      if (childrenLength === 1) {
        props.children = children;
      } else if (childrenLength > 1) {
        //声明一个数组
        var childArray = Array(childrenLength); 
        //依次将children push到数组中
        for (var i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2];
        }

        {
          //冻结array 返回原来的childArray且不能被修改 防止有人修改库的核心对象 冻结对象大大提高性能
          if (Object.freeze) {
            Object.freeze(childArray);
          }
        }
        //父组件内部通过this.props.children获取子组件的值
        props.children = childArray; 
      } 

      //为子组件设置默认值 一般针对的是组件      
      //class com extends React.component 则com.defaultProps获取当前组件自己的静态方法 
      if (type && type.defaultProps) { 
         //如果当前组件中有默认的defaultProps则把当前组件的默认内容 定义到defaultProps中
        var defaultProps = type.defaultProps; 

        for (propName in defaultProps) { 
          if (props[propName] === undefined) { 
            //如果父组件中对应的值为undefinde 则把默认值赋值赋值给props当作props的属性
            props[propName] = defaultProps[propName];
          }
        }
      }

      {
        //一旦ref或者key存在
        if (key || ref) {
          //如果type是组件的话
          var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;
          if (key) {
            defineKeyPropWarningGetter(props, displayName);
          }

          if (ref) {
            defineRefPropWarningGetter(props, displayName);
          }
        }
      }

      //props:
      // 1.config的属性值
       // 2.children的属性(字符串/数组)
       //3.default的属性值
      return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
    }

Vue3.0的实现JSX的源码

function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {
    //传入非法type
    if (!type || type === NULL_DYNAMIC_COMPONENT) {
        if ( !type) {
            warn(`Invalid vnode type when creating vnode: ${type}.`);
        }
        type = Comment;
    }
    if (isVNode(type)) {
        // createVNode接收到已经存在的vNode 比如:<component :is="vnode"/>
        // 确保在克隆期间合并refs,而不是覆盖它
        const cloned = cloneVNode(type, props, true /* mergeRef: true */);
        if (children) {
            normalizeChildren(cloned, children);
        }
        return cloned;
    }
    // 类组件标准化
    if (isClassComponent(type)) {
        type = type.__vccOpts;
    }
    // class & style处理
    if (props) {
        //对于reactive或proxy对象,我们需要克隆它以启用
        if (reactivity.isProxy(props) || InternalObjectKey in props) {
            props = shared.extend({}, props);
        }
        let { class: klass, style } = props;
        if (klass && !shared.isString(klass)) {
            props.class = shared.normalizeClass(klass);
        }
        if (shared.isObject(style)) {
            //reactive对象需要克隆,因为它们可能会发生突变
            if (reactivity.isProxy(style) && !shared.isArray(style)) {
                style = shared.extend({}, style);
            }
            props.style = shared.normalizeStyle(style);
        }
    }
    // 将vnode类型信息编码
    const shapeFlag = shared.isString(type)
        ? 1 /* ELEMENT */
        :  isSuspense(type)
            ? 128 /* SUSPENSE */
            : isTeleport(type)
                ? 64 /* TELEPORT */
                : shared.isObject(type)
                    ? 4 /* STATEFUL_COMPONENT */
                    : shared.isFunction(type)
                        ? 2 /* FUNCTIONAL_COMPONENT */
                        : 0;
    if ( shapeFlag & 4 /* STATEFUL_COMPONENT */ && reactivity.isProxy(type)) {
        type = reactivity.toRaw(type);
        warn(`Vue received a Component which was made a reactive object. This can ` +
            `lead to unnecessary performance overhead, and should be avoided by ` +
            `marking the component with \`markRaw\` or using \`shallowRef\` ` +
            `instead of \`ref\`.`, `\nComponent that was made reactive: `, type);
    }
    //vNode 包含的值
    const vnode = {
        __v_isVNode: true,
        ["__v_skip" /* SKIP */]: true,
        type,
        props,
        key: props && normalizeKey(props),
        ref: props && normalizeRef(props),
        scopeId: currentScopeId,
        children: null,
        component: null,
        suspense: null,
        ssContent: null,
        ssFallback: null,
        dirs: null,
        transition: null,
        el: null,
        anchor: null,
        target: null,
        targetAnchor: null,
        staticCount: 0,
        shapeFlag,
        patchFlag,
        dynamicProps,
        dynamicChildren: null,
        appContext: null
    };
    // 验证key值,不能为NaN
    if ( vnode.key !== vnode.key) {
        warn(`VNode created with invalid key (NaN). VNode type:`, vnode.type);
    }
    normalizeChildren(vnode, children);
    // normalize suspense children
    if ( shapeFlag & 128 /* SUSPENSE */) {
        const { content, fallback } = normalizeSuspenseChildren(vnode);
        vnode.ssContent = content;
        vnode.ssFallback = fallback;
    }
    if (shouldTrack > 0 &&
        // 避免块节点跟踪自己
        !isBlockNode &&
        // 有当前父块
        currentBlock &&
        //组件节点也应该总是被修补,因为即使组件不需要更新,它也需要将实例持久化到下一个vnode上,以便以后可以正确地卸载它。
        (patchFlag > 0 || shapeFlag & 6 /* COMPONENT */) &&
        //事件标志只是为了hydration
        //如果它是唯一的标志,vnode不应该被认为是动态的,因为处理程序缓存
        patchFlag !== 32 /* HYDRATE_EVENTS */) {
        currentBlock.push(vnode);
    }
  

最后

  在vue中JSX的使用场景还是很多的,比如在大多的ui框架中,都有table的渲染,如果框架通过传入对象数据,然后使用JSX渲染出来,其灵活性就会很高了。比如View UI,即原先的 iView而且不管是React还是Vue都是为了提高开发者的生产力,没有谁好,谁不好。只有在不同的生产环境中,谁更加合适。

  如果有想了解更多的Vue3.0可以查看vue3.0学习

   最后一句,喜欢的点赞支持!


阿秋_在路上
25 声望5 粉丝

前端工程师