当前位置: 首页 > 工具软件 > JSX > 使用案例 >

jsx进阶篇

华涵意
2023-12-01

前言

我们假设你有一定的前端知识,简单使用过React但不了解他的原理以及为什么要用它。

通过这篇文章你会了解到这些问题的答案:

  1. 为什么你自己写的组件要首字母大写?
  2. 为什么能在js文件里写html的语法?
  3. 为什么jsx的方式要比模板强?
  4. 为什么jsx必须要有一个顶层节点?
  5. 为什么jsx中不能用class要用className?
  6. 为什么react-dom要单独作为一个库?
  7. 为什么我即使没在这个js中用到React也需要引入React这个库?

看文章之前请记住:
在jsx中的所有东西都是js。

一、什么是jsx?

首先上一段我们熟悉的jsx代码, 这种在js中写类似html代码的方式就是jsx。

<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

其实jsx是React.createElement(component, props, …children) 函数的语法糖。这段代码编译之后就成了:

React.createElement(
  MyButton,
  {color: 'blue', shadowSize: 2},
  'Click Me'
)

二、那么为什么是这三个参数呢?

仔细观察一下熟悉的DOM代码

<div class='box' id='content'>
  <div class='title'>Hello</div>
  <button>Click</button>
</div>

想一下如何用 JavaScript 对象来表现一个 DOM 元素的结构

{
  tag: 'div',
  attrs: { className: 'box', id: 'content'},
  children: [
    {
      tag: 'div',
      arrts: { className: 'title' },
      children: ['Hello']
    },
    {
      tag: 'button',
      attrs: null,
      children: ['Click']
    }
  ]
}

你会发现每个DOM结构都可以通过DOM名称, DOM属性,DOM子元素来表示,DOM一层套一层形成了DOM树。

三、那么为什么不直接在js中写UI呢?

你比较下html方式的UI表示和js方式的UI表示的代码行数你就知道了:

  1. 太长
  2. 不清晰

所以React用jsx语法让我们能在js中可以用HTML的方式描述UI。但这坨代码肯定不是标准的js是吧,直接运行肯定是不行的,所以需要编译。其实就是树的递归调用啊…想想你写斐波那契数列程序的时候画的图。

<div>
  <h1 className='title'>React 小书</h1>
</div>

=> 

React.createElement(
    "div",
    null,
    React.createElement(
      "h1",
      { className: 'title' },
      "React 小书"
    )
  )
)

四、那么生成的代码怎么变成真的DOM?

光是生成了一堆函数调用并不是真正的DOM,它只是包含了生成一个DOM树所需要的所有信息,我们称它为VDOM(虚拟DOM)。那么用什么东西把它变成真正的DOM呢? 想一下你每次是怎么引入React的?

import React, { Component } from 'react'
import ReactDOM from 'react-dom'

class Header extends Component {
...
}

ReactDOM.render(
  <Header />,
  document.getElementById('root')
)

除了基本的React库,用于扩展的Component组件基类, 这个react-dom是什么? ReactDOM用于渲染组件并构造DOM树,然后插入到页面上的某个挂载点上。

五、那么为什么不把这玩意和react库封装在一起?

因为这个UI信息并不一定要渲染到网页上,比如渲染到Canvas上,渲染到手机上。
● 渲染到canvas上的叫 react-canvas
● 渲染到APP上的叫 ReactNative

总结下过程:

// JSX -> VDOM: 将jsx编译成vdom
let vdom = <div id="foo">Hello!</div>;

// VDOM -> DOM: 将vdom渲染成dom
let dom = render(vdom);

// add the tree to <body>: 将dom插入到挂载点
document.body.appendChild(dom);

六、那么为什么非要一层VDOM呢?

因为原生的DOM操作非常费时, 和 DOM 操作比起来,js 计算是极其便宜,有了Vdom之后我们可以直接在这个js对象上操作,而不用直接与DOM打交道。这样的话可以减少浏览器的重排,极大的优化性能。React会在每次数据变化之后更新DOM,但不是更新真实的DOM,而是存在内存中的JS对象,不管你数据怎么变化,React都可以以最小的代价来更新 DOM。

虚拟DOM是React的一个非常重要的概念,不同的类React框架中对虚拟DOM的实现有差异,造成了其性能的千差万别。VDOM比较复杂,这期不介绍。

七、等等教练,我还有几个个小问题

● 为什么我即使没在这个js中用到React也需要引入React这个库?
答: 还记得在文章开头说过的在js中的所有东西都是js吗? 只要你在这个js文件中用到了jsx语法,那么这个jsx会被翻译成React.createElement()的形式,你看如果你不引入React库,这段代码能执行吗?

● 不对啊,有时候我不需要引入这两个库也能用React,怎么解释?
答: 那是因为你把react和react-dom放在html文件的标签中了, React成为全局变量

● 为什么React的组件名一定要大写?
答: 因为普通的html标签都是小写的, div, a, p,那么React如何区分是已有的HTML标签还是用户自定义的组件呢?就是首字母大小写, 如果你小写你的组件名称,react会把它当原生html标签,然后报错因为找不到

● 为什么组件必须要有一个顶层节点?答:React15以下组件需要包一个顶层节点,否则会报Adjacent XJS elements must be wrapped in an enclosing tag 的错,为什么呢? 再复习一遍在js当中所有东西都是js ,并列的两个tag 会渲染成什么样子?React.createElement(…) React.createElement(…) 并不符合语法,但如果做成数组形式返回其实是可以的,因此React16中终于支持了返回数组的写法。这个问题的issue14年就已提出来了,有兴趣的同学可以研究一下。

render() {
  return [
    <li key="A">First item</li>,
    <li key="B">Second item</li>,
    <li key="C">Third item</li>,
  ];
}

八、为什么要用jsx?

说了那么多,我还是觉得jquery还有vm模板好用,那么我为什么要迁移到React + jsx中来呢?
=> 因为一方面用jsx+React我们可以使用js的所有语法和能力,而使用模板引擎通常只能使用其提供的有限的模板语法。

举个栗子,循环列表,在vm中我们只能用这样的语法写:

<ul> 
  #foreach ( $product in $allProducts ) 
    <li> $product </li> 
  #end 
</ul>

而在jsx中, 我们可以:

// 用map写
let list = items => items.map( p => <li> {p} </li> );
// 用循环写
let list = [];
for (let i = 0 ; i < items.length: i++) {
  list.push(<li>{items[i]}</li>);
}
// 用forEach写
let list = [];
items.forEach(item => list.push(<li>{item}</li>))

// 用while写
// 用for...of写
// ...

总之你爱怎么写就怎么写。这极大地拓展了前端写界面的能力,前端同学心里美滋滋。

另一方面jsx结合React能发挥它前端组件化的优势,提高代码复用率,避免手动操作DOM等,这里不赘述。

九、等下你这些代码里的{}是什么? 为什么不直接在{}里写循环套jsx

这个是jsx提供给你插入表达式的,包括变量,表达式计算,函数调用都可以放在里面。如何判断是否是表达式? 看他可不可以放在等于号右边。

所以if, for这样的就不是表达式了,所以我们也不能在{}中写for循环和if判断,但我们可以把结果暂存到变量中,然后插入到{}中,或者用?表达式啊。

另外这个表达式不仅可以用在标签内部,还能用在标签的属性上,属性props是jsx的一个重要概念。

十、props?属性? 为什么需要属性

首先我们聊聊为什么要组件化,组件化是为了代码的复用和分治。比如你的同事写了一个红色的按钮组件,有了React,我只要引入一下就可以用到我的工程里了,太棒了。但是等等,我的工程里需要的是一个蓝色的按钮,难道我要去改组件的代码?我希望我可以传一个颜色参数进去改变组件的样式,而这个需求的实现方式就是props。你可以理解为函数的参数,传入不同的参数,输出不同的值,没有参数的函数功能太过限定了。
所以 props的作用是让外部能对组件自己进行配置。

<div className="sidebar" color="red"/>

注意props一旦传入到组件中,它就是只读的,不可以再赋值。如果 props 渲染过程中可以被修改,那么就会导致这个组件显示形态和行为变得不可预测,这样会可能会给组件使用者带来困惑

十一、咦,这个className是什么鬼,为什么不用class?

让我们再来复习一下开篇的话: 在jsx中的所有东西都是js。当然不止class,同样的还有htmlFor替代for。

React.createElement(
  MyButton,
  {class: 'blue', shadowSize: 2},
  'Click Me'
)

通常的说辞是class是js的保留字。不过翻译成js好像没什么问题? 即使class是js的保留词依然可以用在属性语法中才对,只是不能做变量标识。是的,就有类react框架preact直接用的class,详见issue,甚至还有自动用babel帮我们做转换的jsx-html-class

React中用className的原因参考quora,总结一下原因有两点:

  1. 我们在操作html属性的时候一般用的是el.className=…,而不是el.setAttribute(‘class’, …),Attributes一般赋值字符串,而属性名可以赋值对象,更灵活。所以jsx中的className和HMTL的className属性表现一致,没毛病
  2. 未来react可能会用…来解构this.props,这时候class和for作为保留字不能作为变量标识,就不能适用这种情况了。

十二、老司机求带啊,有没有其他React技巧

来来来,新鲜的React技巧便宜卖,10元一条,请扫我的付款码…好吧,其实都是jsx-in-depth里的。
● 属性可以用字符串字面量, 会进行解码

<MyComponent message="&lt;3" />
===
<MyComponent message={'<3'} />

● 如果你不给属性赋值,那么默认值为true。这和html中的语义是一样的,但不建议使用,还是下面的直观

<MyTextBox autocomplete />
===
<MyTextBox autocomplete={true} />

● 善用…,如果属性在对象中,解构对象轻松搞定

function App2() {
  const props = {firstName: 'Ben', lastName: 'Hector'};
  return <Greeting {...props} />;
}

● 布尔值,null,undefined作为子组件不会渲染,如果你想渲染这些值可以先转成string,如{String(myVariable)}。这个可以用来做条件渲染,注意showHeader得是一个boolean值,不能是0这样的伪false值。

<div>
  {showHeader && <Header />}
  <Content />
</div>
 类似资料: