jsx核心知识详情

1.认识jsx

​ jsx实际上是JavaScript语法上的一个扩展,也是JavaScript XML的缩写。它的作用就是用来描述我们的用户界面,使有关界面的代码更好的与JavaScript代码融合在一起。

jsx不同于vue的模板语法,在vue中我们经常将一些指令嵌入在html代码中来完成一些逻辑,例如v-for,v-show等等。在react中我们使用jsx,然后直接使用js的代码进行实现我们的需求。这样做的目的使更加灵活,相比之下,vue模板指令就有了一定的局限性。

1.1 react为什么使用jsx

官方给出:

  1. JSX在定义类似HTML这种树形结构时,十分的简单明了。
  2. 简明的代码结构更利于开发和维护。
  3. XML有着开闭标签,在构建复杂的树形结构时,比函数调用和对象字面量更易读。

我们来自己总结一下:

​ 这就和react的理念有关系了,react认为渲染的逻辑本质上与UI逻辑之间存在耦合,他们之间的关系可以说是密不可分的,因此在react下,它的开发模式并没有将标记分离。这也是All in Js含义的一部分。

​ 当然我们也可以不使用jsx来进行UI的开发,我们可以使用react为我们提供的createElement函数的方式来进行DOM树的构建

var child1 = React.createElement('li', null, 'First Text Content');
var child2 = React.createElement('li', null, 'Second Text Content');
var root = React.createElement('ul', { className: 'my-list' }, child1, child2);

​ 但是这样做的代码可读性真的比较差,因此,react官方就推出了jsx,react的作者也极力推荐开发者使用jsx。例如以上的代码可以用jsx表示为以下内容:

var root =(
  <ul className="my-list">
    <li>First Text Content</li>
    <li>Second Text Content</li>
  </ul>
);

​ 以上的两块代码实际上原理使相同的,后者是使用jsx的模式进行构建界面之后再通过转义器将其转为JavaScript代码由浏览器执行。这里的转义器通常使用babel。

因此我们可以得出一个一个结论:

jSX本身并不是什么高深的技术,可以说只是一个比较高级但很直观的语法糖。它非常有用,却不是一个必需品,没有JSX的React也可以正常工作。只是方便与不方便的问题。

1.2 使用前须知

我列举了几点关于再使用jsx前的需要注意的点:

语法支持:

我们的jsx的内容是在是与js代码的内容再一起的,因此就在script标签内部。

我们需要为script标签的type属性设置为text/babel,因为这是react独有的jsx语法,凡是只要是使用jsx的地方都必须加上这个属性。

关于依赖库:

对于初识react的人来说,一般使用cdn引入库,我们知道通常使用三个依赖库:分别是react.js 、react-dom.js 和 browser.min.js,他们必须首先被加载

react.js: react的核心库

react-dom.js: 提供与DOM相关的功能

browser.min.js:将jsx的语法转为JavaScript的代码

关于关键字:

​ 在实际开发中,JSX在产品打包阶段都已经编译成纯JavaScript,JSX的语法不会带来任何性能影响。另外,由于JSX只是一种语法,因此JavaScript的关键字class, for等也不能出现在XML中,而要如例子中所示,使用className, htmlFor代替,这和原生DOM在JavaScript中的创建也是一致的。

2. jsx基操

​ 如果我们jsx中的内容是动态的,我们可以通过表达式来获取。书写规则就是一堆大括号,大括号内可以是任意的变量、字符串或者表达式。

2.1 jsx中的注释

为什么要连最基本的注释都要将呢?因为再jsx中用错了注释会报错。

因为jsx是嵌入在JavaScript中的一种语法,所以再编写jsx时我们需要通过jsx的语法啦编写

<div>
{/*注释*/}
<h2>{this.state.counts}</h2>
</div>

2.2 jsx嵌入变量

嵌入变量这里一般分为三种情况:

  1. 当变量是Number、String、Array等类型时是正常显示的
  2. 当变量是Null、undefined、Boolean类型时,内容为空(如果希望显示,则转为字符串即可)
  3. react规定变量不能为对象类型

我们可以看一下第三点:

 this.state = {
        firend: {
          name: '张三'
        }
      }
<p>{this.state.firend}</p>

也就是说上面的代码是会报错的,会报这个 Objects are not valid as a React child

2.3 jsx嵌入表达式

具体可以嵌入什么表达式?

一般我们经常会加一些运算操作、三元运算或者是执行一些函数。

  render() {
      const {firstName,lastName,age}=this.state;
      return (
        <div>
          <p>{firstName+' '+lastName}</p>
          <p>{age>18?'成年':'未成年'}</p>
          <p>{this.print('haha')}</p>
        </div>
      )
    }

2.4 jsx绑定属性

​ 使用jsx动态的绑定属性实际上做法也是使用嵌入表达式的形式,我们也可以控制属性的动态展示,这里就相当的灵活了,我们可以使用任意操作来进行属性的定义。

  • 因为我们jsx实际上是js操作虚拟dom的语法糖,因此我们的有些关键字就不能与js的冲突,例如class属性、label中的for等。

  • 当我们需要改变为dom增添class属性时,需要使用className而不是class。for要使用htmlFor。

  • 也可以添加style属性,需要注意的是style后面跟的是一个对象类型,对象中是样式的属性名和属性值;

   render() {
      const { title, imgUrl, flag } = this.state
      return (
        <div>
          <h2 title={title}>React Demo</h2>
          <img src={getimgUrlSize(imgUrl, 100)} alt="" />
          <div className='box'>div</div>
          <div className={"box " + (flag ? 'activity':'')}>我也时div元素</div>
          <div style={{color:'red'}}>ttttt</div>
        </div>
      )
    }

3. jsx事件绑定

​ 再react中的事件绑定,事件名称时采用小驼峰式的写法。我们也需要一对{}传入一个事件处理函数。这个函数会在发生时执行。

render() {
      return (
        <div>
          <h2>React Demo</h2>
          <button onClick={this.btnfun}>button</button>
        </div>
      )
    }
    btnfun(){
      console.log('zhixing');
    }

上面就是一个基本的写法。

3.1 jsx之间绑定中的this指向问题

当时如果这是我有一个需求,我点击以下按钮,来获取我们state中的数据。

btnfun(){
    console.log(this);//undefined
    console.log(this.state.data);//not undefined
}

这里为什么会出现这个问题呢?因为btnfun()函数并不是我们主动调用的,而是当触发button的事件时,react内部再去调用这个函数。而react的内部时不知道这里的this是指向哪里的。

为了解决这个问题,我们给出了三种解决方案:

方案一:使用bind为事件方法绑定this:

在使用事件方法时直接通过bind改变事件方法的指向,就像如下代码一样:

 <button onClick={this.btnfun.bind(this)}>button</button>
   btnfun(){
      console.log(this);//undefined
      console.log(this.state.data);//not undefined
    }

这是当react内部调用我们的事件函数时,就可以得知函数中的this指向何处。

上面的写法是一解决方案,但是当我们有 若干个事件都要去执行这个函数怎么办呢?我们可以使用下面这种方式进行优化。

 constructor(props) {
      super(props),
      this.state={
        data:'message'
      },
      this.btnfun=this.btnfun.bind(this)
    }

我们可以将为事件方法绑定this的操作放在构造器里,这样在后面使用时就直接使用this.btnfun 即可。

方案二:使用ES6的class fields语法

​ 这种语法是给类定义属性的方法,称之为class field语法。因为这里在赋值时使用了箭头函数,而箭头函数的this在任何情况下都会去上一个作用域中查找,而我们事件方法的上一个作用域恰巧就是当前的对象。

 btnfun=() => {
      console.log(this);//当前对象	 	
      console.log(this.state.data);//正常输出
    }

这里须知:

箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。

方案三:事件监听时传入箭头函数(推荐)

​ 这种方式其实就是当给onClick事件中直接传入一个箭头函数,然后在箭头函数中进行我们的操作,可以直接写在函数中,也可以调用外部的函数。这里的this也就是箭头函数中的this,指向的时上一个作用域的this,而这里也会发生隐式绑定,将this的指向变为我们想要的结果。

写法如下:

  render() {
      return (
        <div>
          <h2>React Demo</h2>
          {/*<button onClick={this.btnfun}>button</button>*/}
          <button onClick={() => this.btnfun()}>button</button>
        </div>
      )
    }
    btnfun() {
      console.log(this);//undefined
      console.log(this.state.data);//not undefined
    }

3.2 事件参数传递

在处理事件时,有时候也会有一些需要传递的参数,最常见的就是event对象。在jsx中其实在我们的事件方法中默认就给我们传递了event对象

如下代码所示:

  render() {
      return (
        <div>
          <h2>React Demo</h2>
          <button onClick={this.btnclick}>button</button>
        </div>
      )
    }
    btnclick(e){
      console.log(e);//打印event对象
    }
  }

​ 但是如果我们还想要传递一些其他的参数呢?还记得我们this绑定问题的方案3吗,我们可以使用上方法进行参数的传递,包括event对象(是箭头函数的参数)

看以下需求:我们想要输出一个列表,并且点击单个li标签,打印对象的item和index以及event对象。

  class App extends React.Component {
    constructor(props) {
      super(props),
        this.state = {
          movies: ['A', 'B', 'C', 'D']
        }
      this.btnclick = this.btnclick.bind(this)
    }
    render() {
      return (
        <div>
          <h2>React Demo</h2>
          <ul>
            {
              this.state.movies.map((item, index) => {
                return <li onClick={(e) => {
                  this.liclick(item, index,e)
                }}>{item}</li>
              })
            }
          </ul>
        </div>
      )
    }
    liclick(item, index,e) {
      console.log('li发生了点击', item, index,e);
    }
  }

4. jsx渲染

​ 关于react的渲染值得一提就是条件渲染了吧,条件渲染即判断dom是否渲染。熟悉vue的同学应该知道在vue中,条件渲染通常使用v-if或是v-show的指令来完成。但是在react中,我们通常使用普通的JavaScript代码来进行操作。

我们可以看一下面的例子:

需求:判断登录未登录,以及文本的切换:

class App extends React.Component {
    constructor(props) {
      super(props)
      this.state = {
        flag: false
      }
    }
    render() {
      let dom = this.state.flag ? <h2>欢迎您</h2> : <h2>请登录</h2>
      return (
        <div>
          {dom}
          <button onClick={() => {
            this.btnclick()
          }}>{this.state.flag ? '退出' : '登录'}</button>
          <hr/>
          {/* 逻辑与判断 */}
          <div>{this.state.flag &&'你好啊'}</div>
        </div>
      )
    }
    btnclick(){
            this.setState({
              flag: !this.state.flag
            })
          }
  }

jsx渲染还包括列表渲染等,其中就包括了一些js高阶函数的使用。

5.Jsx原理解析

​ 实际上jsx就是React中一个函数的语法糖,这个函数就是CreateElement,前面提到我们通常在引入React时会引入三个文件,分别是分别是react.js 、react-dom.js 和 browser.min.js,bebel在这里的作用就是将jsx语法转义为React可以识别的代码。

​ 我们可以看一下以下代码通过Bebel转义后的结果:

 <div>
          <h2>React Demo</h2>
          <div className="header">
            <h1>我是标题</h1>
          </div>
          <div className="content">
            <p>content</p>
            <button>按钮</button>
          </div>
          <div className="footer">Footer</div>
        </div>

可以通过Babel官方的转换工具(Babeljs.io),注意勾选react选项。
在这里插入图片描述

​ 上面代码编译完成输出以下结果:

/*#__PURE__*/
React.createElement("div", null, /*#__PURE__*/React.createElement("h2", null, "React Demo"), /*#__PURE__*/React.createElement("div", {
  className: "header"
}, /*#__PURE__*/React.createElement("h1", null, "\u6211\u662F\u6807\u9898")), /*#__PURE__*/React.createElement("div", {
  className: "content"
}, /*#__PURE__*/React.createElement("p", null, "content"), /*#__PURE__*/React.createElement("button", null, "\u6309\u94AE")), /*#__PURE__*/React.createElement("div", {
  className: "footer"
}, "Footer"));

​ 我们不难看出jsx最终被编译成上图的第二种形式,也就是说我们也可以不使用jsx,直接使用createElement函数,当时这样写也就无疑时增加了开发的难度。

createElement函数:

这个函数通常接收三个参数

参数一:type,是当前参数的类型,如果是标签元素就是用字符串来表示标签名,如果是组件元素,就直接使用组件的名称。

参数二:config,所有jsx的属性都以对象的形式以键值对出现

参数三:children,存放在标签中的内容

children参数详情:

​ 我们可以从上面转义后的代码中可以看出,最上层的为div标签,但是我们的最上层的容器有四个平级元素,这就导致我们的children参数有四个,之后子元素的在进行嵌套,这就是我们整个流程。

它的底层是这样处理的: 在这里插入图片描述

​ 通过argument参数变量来判断当前的参数长度再去判断children的个数,如果children个数为1,则直接将其挂载在props对象上。如果不为1,就是有多个参数的情况,通过for将children的值赋给新定义的childArray。因此props的children的值的类型是根据它的长度不同而变化的,长度为1时就是一个对象(ReactElement对象),当长度>1是就是一个数组。

6.react的虚拟DOM

​ 在react渲染DOM时使用的reactDOM.render函数渲染的并不是真实的DOM,而是JavaScript对象,也就是虚拟的DOM。

​ 在上一讲我们提到了,当createElement函数的第三个参数为一个时props的children值会是一个对象,也就是说crateElement函数返回一个对象,我们可以看一下源码:

 return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );

在react的源码中createElement函数返回了一个ReactElement对象。事实上这个对象的作用就是react为了组成一棵JavaScript的对象树而设定的。也就是我们所说的虚拟DOM。

我们可以打印一下上一节研究createElement函数的代码:

 render() {
      let vdom=<div>
          <h2>React Demo</h2>
          <div className="header">
            <h1>我是标题</h1>
          </div>
          <div className="content">
            <p>content</p>
            <button>按钮</button>
          </div>
          <div className="footer">Footer</div>
        </div>
      console.log(vdom);
      return vdom;
    }

最终的打印结果如下图:
在这里插入图片描述

很明显我们可以看出,这就形成了一颗层级分明的JavaScript的树结构。

转为真实dom:

既然jsx为我们形成了一颗虚拟dom,那么我们如何将虚拟dom转为真实的dom呢?

开头我们也提到了,就是使用reactDOM.render函数,这个函数就是将我们的虚拟dom映射到我们真实的dom中去。

总结:

虚拟DOM从创建到渲染整个流程:

jsx->createElement函数->ReactElement(虚拟对象树)->ReactDOM.render->真实DOM

其实react native(RN)中的渲染dom的流程和这个时相似的,只是映射的真实dom不用:

jsx->createElement函数->ReactElement(虚拟对象树)->ReactDOM.render->原生控件(IOS/Android)

7.为什么使用虚拟DOM

为什么要采用虚拟的DOM而不是直接进行修改真实的DOM呢?

  • 真实的DOM很难追踪状态,不方便针对应用程序进行调试
  • 操作真实的DOM性能较低,传统的开发模式会进行频繁的DOM操作,这会造成性能较低的现象。
  • DOM操作很慢,轻微的操作都可能导致⻚面重新排 版,⾮常耗性能

然而相对于DOM对象,js对象处理起来更快, 而且更简单。通过diff算法对比新旧vdom之间的差异,可以 批量的、最⼩化的执行dom操作,从而提高性能

官网给我们的描述是:

虚拟DOM是一种编程理念,在这种理念中UI是以理想化或者说虚拟化的方式保存在内存中,并且他是一个相对简单的JavaScript对象;我们可以通过ReactDOM.render让虚拟DOM和真实DOM同步起来,这个过程叫做协调

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值