写点什么

Vue 2x 中使用 render 和 jsx 的最佳实践 (2)

作者:默默的成长
  • 2022-10-16
    山东
  • 本文字数:4455 字

    阅读完需:约 1 分钟

JSX 书写规范

JSX 支持换行


let jsx = (    <div>      <h1>hello world</h1>        <button/>    </div>)
复制代码


  • JSX 的顶层只能有一个根元素,我们很多时候会在最外层包裹一个div(后续 React 推出了不占据 Dom 结构的Fragment,同时,<></>空标签有同样的效果,但是测试后发现这个在 vue 中不生效,)

  • 为了方便阅读,我们通常在 jsx 的外层包裹一个小括号(),这样可以方便阅读,并且 jsx 可以进行换行书写;

  • JSX 中的标签可以使单标签,也可以是双标签如果是单标签,必须以/>结尾

JSX 注释都要用花括号{}包起来

{    //我是单行注释}
{/*我是一段注释*/}
复制代码

JSX 插入变量

const t = 'hello world';let jsx = (    <div>      <h1>{t}</h1>  <button/>    </div>)
复制代码

JSX 嵌入表达式

  • 运算表达式


const arg1 = 1;const arg2 = 2;let jsx = (    <div>      <h1>{arg1 + arg2}</h1>  <button/>    </div>)
复制代码


  • 三元表达式


const t = 'hello world';const arg1 = 1;const arg2 = 2;const hasButton = true;let jsx = (    <div>      <h1>        {            t === 'hello world' ?  arg1 : arg2        }        </h1>  {            //如果hasButton为true,则渲染button组件            hasButton && <button/>        }    </div>)
复制代码


  • 函数调用


const t = 'hello world';const arg1 = 1;const arg2 = 2;const hasButton = true;const func1 = ()=>{ return (<div>func1</div>) }let jsx = (    <div>      <h1>        {            //如果在render外定义的函数,请注意加this:this.func1()            func1()        }        </h1>    </div>)
复制代码

JSX 绑定属性

  • 绑定普通属性 Attrs


const title = 'hello world';let jsx = (    <div>      <h1 title={title}>hello world</h1>        <button/>    </div>)
复制代码


  • 绑定 class


在 jsx 中,class 属性需要指定为 className,因为 class 在 JavaScript 中是保留字段


const hasCss = true;const h1Css = [    'flex',    hasCss ? 'css' : 'noCss',]let jsx = (    <div>        <h1 className='flex'>hello world</h1>      <h1 className={h1Css}>hello world</h1>        <button/>    </div>)
复制代码


  • 绑定 style


在 jsx 中,windows 风格的命名方式(属性、style、方法、event)都需要转换成驼峰式的写法,比如,正常写一个 style 指定左边的外边距:margin-left:‘10px’,


这里需要改成:marginLeft:‘10px’


const title = 'hello world';let jsx = (    <div>      <h1 style={{ marginLeft:'10px',width:'100%' }}>hello world</h1>        <button/>    </div>)
复制代码

JSX 绑定事件

  • JSX 中绑定事件类似在 HTML 原生中绑定事件,只不过 React 中事件命名采用小驼峰(camelCase),而不是纯小写;

  • 但是我们会发现在我们绑定的回调事件中访问我们对应的 this 会是 undefined,这是因为对应的回调函数是 React 内部帮我们去进行调用的,React 无法确定对应的 this 所以采用的是 callback.apply(undefined,[])方式调用,改变了 this 的指向为 undefined。[这条规则不适用于Vue,因为在Vue中对this做了特殊处理]


function func1(){    console.log(this); // undefined}
render(){ let jsx = ( <div> <button onClick={this.func1}/> </div> ) return jsx;}
复制代码


如果我们需要在事件中通过this来访问React组件本身属性和方法,有以下几条解决方案:


  • 通过 bind 绑定 this(显示绑定)


function func1(arg1, arg2, e){    console.log(this); // ReactCom        console.log(arg1); // param1        console.log(arg2); // param2        console.log(e); // Event from buttonClick}
render(){ let jsx = ( <div> <button onClick={this.func1.bind(this,'param1','param2')}/> </div> ) return jsx;}
复制代码


使用 bind 绑定的方式除了可以非常简单的获取到事件对象 event 之外,还可以传递我们想要传递的参数


  • 除了显示绑定之外,我们可以使用匿名函数(箭头函数)的方式


function func1(arg1, arg2, e){    console.log(this); // ReactCom        console.log(arg1); // param1        console.log(arg2); // param2        console.log(e); // Event from buttonClick}
render(){ let jsx = ( <div> <button onClick={(e)=> { this.func1('param1','param2', e); }}/> </div> ) return jsx;}
复制代码


  • 同理,在声明函数的时候我们使用箭头函数的方式也可以达到同样效果[如果想要传递我们自己的参数,还是需要用到bind]


const func1 = (e) => {    console.log(this); // ReactCom        console.log(e); // Event from buttonClick}
render(){ let jsx = ( <div> <button onClick={this.func1}/> <button onClick={this.func1.bind(this,'param1')}/> </div> ) return jsx;}
复制代码


这里高阶的同学要注意!


如果是在 JSX 中使用事件绑定,请不要使用箭头函数的方式去声明方法甚至直接在 JSX 中使用箭头函数绑定事件。


因为根据 VR 的 render 渲染机制,如果使用箭头函数,那么每当组件的 state 发生改变,推动 render 渲染执行的时候,如果存在箭头函数,每次浏览器都会分配新的内存和额外的开销来执行事件的绑定,组件绑定的层级越深,额外开销越大。


所以,为了最优的性能考虑,请在constructor构造函数中对需要绑定的事件做显示绑定



constructor() { this.func1 = this.func1.bind(this);}
function func1(e){ console.log(this); // ReactCom console.log(e); // Event from buttonClick}
render(){ let jsx = ( <div> <button onClick={this.func1}/> </div> ) return jsx;}
复制代码

JSX 条件渲染

  • jsx中,不允许使用ifif-else,请使用三元运算符或者逻辑与&&

  • 同样,也允许使用for循环,请使用 JS 中的高阶函数mapfilter……


const t = 'hello world';const arg1 = 1;const arg2 = 2;const hasButton = true;const list = [1,2,3,4,5,6,7,8,9];let jsx = (    <div>      <h1>        {            t === 'hello world' ?  arg1 : arg2        }        </h1>  {            //如果hasButton为true,则渲染button组件            hasButton && <button/>        }        <ul>        {            list.map((item) => <li>{item}</li>)        }        </ul>    </div>)
复制代码

createElement

要更透彻的了解和学习 JSX,浅析其本质,那么一定要先了解createElement


因为提到JSX,不可避免的需要提到createElement,所以,是不是奇奇怪怪的知识又增加了 : )

从React中看createElement

JSX 实际上仅仅是 React.createElement(type, config, children)方法的语法糖,该方法接收三个参数:


  • type

  • 当前 ReactElement 的类型,如果是标签元素,那么使用字符串表示“div”,如果是组件元素直接使用组件的名称就可以。

  • config

  • 我们在 JSX 中绑定的属性会在 config 对象中以键值对的形式存在。

  • children

  • 存放标签中的内容,以 children 数组的形式存储


我们都知道,JSX 是通过babel进行解析的,而我们编写 JSX 的时候必须依赖babel


我们可以再 babel 的官网查看 JSX 的转换过程:传送门


<!-- 转换示例代码 --><div>        <h1 className='flex'>hello world</h1>      <h1 style={{marginLeft:'10px'}}>hello world</h1>        <button/></div>
复制代码



如果我们直接使用 React.createElement()来编写代码,就不需要以来 bable 进行解析也可以正常的渲染显示


render(){    return 'use strict';    /*#__PURE__*/    React.createElement("div", null, /*#__PURE__*/React.createElement("h1", {      className: "flex"    }, "hello world"), /*#__PURE__*/React.createElement("h1", {      style: {        marginLeft: '10px'      }    }, "hello world"), /*#__PURE__*/React.createElement("button", null));}
复制代码


我们通过 React.createElement()方法最后返回得到的是一个ReactElement对象,


这个ReactElement对象作用是什么?


其实 React 利用ReactElement对象组成了一个 JavaScript 对象树,这个对象树就是我们经常讲的一个概念--虚拟DOM(VR DOM),我们可以将之前 jsx 返回的结果进行打印来查看对应的 ReactElemnt 对象:


render(){  const arg1 = 1;  const arg2 = 4;  let jsx = (      <div>        <div>{arg1 + arg2}</div>        <div className="flex">            <button/>        </div>        <div className={{marginLeft:'10px'}}>hellow world</div>      </div>  )  console.log(jsx);  return jsx;},
复制代码



我们编写的 JSX 代码经过 bable 编译解析成对应的React.createElement()方法形式,


经过React.createElement()方法调用返回我们对应的ReactElement对象树(虚拟 DOM 树),对应的ReactElement对象树经过ReactDOM.render()方法转换为真正的 DOM 在我们的浏览器进行渲染。


JSX -> VR DOM -> DOM

为什么要用VR DOM

  • 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;

  • 操作真实 DOM 性能较低:传统的开发模式会进行频繁的 DOM 操作,而这一的做法性能非常的低;

  • DOM 操作非常耗费性能

  • document.createElement 本身创建出来的就是一个非常复杂的对象:传送门

  • DOM 操作会引起浏览器的回流和重绘,所以在开发中应该避免频繁的 DOM 操作

不是用了VR DOM性能就一定会变好

React 从来没有说过 “React 比原生操作 DOM 快”。


React 的基本思维模式是每次有变动就整个重新渲染整个应用。


如果没有 Virtual DOM,简单来想就是直接重置 innerHTML。


很多人都没有意识到,在一个大型列表所有数据都变了的情况下,重置 innerHTML 其实是一个还算合理的操作... 真正的问题是在 “全部重新渲染” 的思维模式下,即使只有一行数据变了,它也需要重置整个 innerHTML,这时候显然就有大量的浪费。


我们可以比较一下 innerHTML vs. Virtual DOM 的重绘性能消耗:


  • innerHTML: render html string O(template size)   + 重新创建所有 DOM 元素 O(DOM size)

  • Virtual DOM: render Virtual DOM + diff O(template size)  + 必要的 DOM 更新 O(DOM change)


Virtual DOM render + diff 显然比渲染 html 字符串要慢。


但是!它依然是纯 js 层面的计算,比起后面的 DOM 操作来说,依然便宜了太多。


可以看到,innerHTML 的总计算量不管是 js 计算还是 DOM 操作都是和整个界面的大小相关,但 Virtual DOM 的计算量里面,只有 js 计算和界面大小相关,DOM 操作是和数据的变动量相关的。


前面说了,和 DOM 操作比起来,js 计算是极其便宜的。这才是为什么要有 Virtual DOM


它保证了:


  • 不管你的数据变化多少,每次重绘的性能都可以接受;

  • 你依然可以用类似 innerHTML 的思路去写你的应用。

发布于: 刚刚阅读数: 4
用户头像

还未添加个人签名 2022-10-11 加入

还未添加个人简介

评论

发布
暂无评论
Vue 2x 中使用 render 和 jsx 的最佳实践 (2)_Vue_默默的成长_InfoQ写作社区