zl程序教程

您现在的位置是:首页 >  前端

当前栏目

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

Vue 使用 实践 最佳 render jsx
2023-06-13 09:14:13 时间

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 &amp;&amp; <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,请使用三元运算符或者逻辑与&amp;&amp;
  • 同样,也允许使用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 &amp;&amp; <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 的思路去写你的应用。