Skip to content
On this page

JSX 语法

JSX:javascript and xml(html) 把 JS 和 HTML 标签混合在了一起「并不是我们之前玩的字符串拼接」

认识 JSX

javascript
const element=<h2>Hello World</h2>
ReactDoM.render (element, document.getElementById('app'));
1
2

这段 element 变量的声明右侧赋值的标签语法是什么呢?

  • 它不是一段字符串(因为没有使用引号包裹),它看起来是一段 HTML 原生,但是我们能在 js 中直接给一个变量赋值 html 吗?
  • 其实是不可以的,如果我们讲 type="text/babel" 去除掉,那么就会出现语法错误;
  • 它到底是什么呢?其实它是一段 jsx 的语法;

JSX 是什么

  • 是一种 JS 和 HTML 混合的语法,将组件的结构、数据甚至样式都聚合在一起定义组件
  • JSX 是一种 JavaScript 的语法扩展(eXtension),也在很多地方称之为 JavaScript XML,因为看起就是一段 XML 语法
  • JSX 其实只是一种语法糖,最终会通过 babeljs 转译成 createElement 语法,以下代码等价

为什么 React 选择了 JSX

  • React 认为渲染逻辑本质上与其他 UI 逻辑存在内在耦合
    • 比如 UI 需要绑定事件(button、a 原生等等);
    • 比如 UI 中需要展示数据状态,在某些状态发生改变时,又需要改变 UI;

JSX 的书写规范

  • JSX 的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个 div 原生(或者使用后面我们学习的 Fragment);
  • 为了方便阅读,我们通常在 jsx 的外层包裹一个小括号 (),这样可以方便阅读,并且 jsx 可以进行换行书写;
  • JSX 中的标签可以是单标签,也可以是双标签; √ 注意:如果是单标签,必须以 />结尾;

vscode 如何支持 JSX 语法「格式化、快捷提示。..」

  • 创建的 js 文件,我们把后缀名设置为 jsx 即可,这样 js 文件中就可以支持 JSX 语法了
  • webpack 打包的规则中,也是会对。jsx 这种文件,按照 JS 的方式进行处理的

在 ReactDOM.createRoot() 的时候

不能直接把 HTML/BODY 做为根容器,需要指定一个额外的盒子「例如:#root」

每一个构建的视图,只能有一个“根节点”

  • 出现多个根节点则报错 Adjacent JSX elements must be wrapped in an enclosing tag.
  • React 给我们提供了一个特殊的节点(标签):React.Fragment 空文档标记标签
html
  <></>
1

既保证了可以只有一个根节点,又不新增一个 HTML 层级结构!!

jsx 中的注释

jsx
 {/* 我是一段注释 */}
1

JSX 嵌入变量

情况一

当变量是 Number、String、Array 类型时,可以直接显示

除数组对象外,其余对象一般都不支持在{}中进行渲染,但是也有特殊情况:

  • JSX 虚拟 DOM 对象
  • 给元素设置 style 行内样式,要求必须写成一个对象格式
  • 数组对象:把数组的每一项都分别拿出来渲染「并不是变为字符串渲染,中间没有逗号」
  • 函数对象:不支持在{}中渲染,但是可以作为函数组件,用方式渲染!!

情况二

当变量是 null、undefined、Boolean 类型时,内容为空; - 如果希望可以显示 null、undefined、Boolean,那么需要转成字符串;

- 转换的方式有很多,比如 toString 方法、和空字符串拼接,String(变量)等方式;

情况三:

对象类型不能作为子元素(not valid as a React child)

给元素设置样式

  • 行内样式:需要基于对象的格式处理,直接写样式字符串会报错
jsx
<h2 style={{
    color: 'red',
    fontSize: '18px' //样式属性要基于驼峰命名法处理
}}>
1
2
3
4
  • 设置样式类名:需要把 class 替换为 className
jsx
<h2 className="box">
1

JSX 嵌入表达式

  • 运算表达式
jsx
 <h2>{ firstname + " " + lastname }</h2>
1
  • 三元运算符
jsx
 <h2>{ isLogin ? "是~": "否~" }</h2>
1
  • 执行一个函数
jsx
  <h2>{this.getFullName()}</h2>
1

jsx 绑定属性

  • 元素都会有 title 属性
  • img 元素会有 src 属性
  • a 元素会有 href 属性
  • 元素可能需要绑定 class
  • 原生使用内联样式 style

React 事件绑定

  • 如果原生 DOM 原生有一个监听事件,我们可以如何操作呢?

    • 方式一:获取 DOM 原生,添加监听事件;
    javascript
        var btn = document.getElementById('btn')
        btn.addEventListener('click', myClick)
    
    1
    2
    • 方式二:在 HTML 原生中,直接绑定 onclick;
    html
      <button onclick="myClick()"
        <script>
            function myClick() {
                console.log('你终于点中了我...')
            }
        </script>
    
    1
    2
    3
    4
    5
    6
    javascript
        var btn = document.getElementById('btn')
        // DOM对象的事件属性
        btn.onclick = myClick;
        function myClick() {
        console.log('你终于点中了我...')
        }
    
    1
    2
    3
    4
    5
    6

在 React 中是如何操作呢?

  • 我们来实现一下 React 中的事件监听,这里主要有两点不同
    • √ React 事件的命名采用小驼峰式(camelCase),而不是纯小写;
  • 我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行;
  • 你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault
jsx
class App extends React.Component{
    constructor (props){
        super(props);
        this.state={
            counter: 0
        }
    }
    render(){
        return (
            <div>
                <h2>当前计数:{this.state.counter }</h2>
                <button onClick={this.increment.bind(this)}>+1</button>
                <button onClick={this.decrement.bind(this)}>-1</button>
            </div>
        )
    }
    increment(e){
        e.preventDefault();
        this.setState({
            counter:this.state.counter+1
        })
    }
    decrement(){
        this.setState({
            counter:this.state.counter-1
        })
    }
}

ReactDoM.render(<App/>,document.getElementById( "app")):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

this 的绑定问题

  • 方案一:bind 给 btnClick 显示绑定 this
jsx
class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {}
        this.btnClick = this.btnClick.bind(this);
    }
    render() {
        return (<button onClick={this.btnClick}>按钮1</button>)
    }
}
1
2
3
4
5
6
7
8
9
10
  • 方案二:使用 ES6 class fields 语法
jsx
class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {}
        this.btnClick = this.btnClick.bind(this);
    }
    render() {
        return (<button onClick={this.btnClick}>按钮1</button>)
    }

    increment = () => {
      console.log(this.state.counter);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  • 方案三:事件监听时传入箭头函数(推荐)
jsx
class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {}
        this.btnClick = this.btnClick.bind(this);
    }
    render() {
        return (<button onClick={() => { this.increment() }}>按钮1</button>)
    }
}
1
2
3
4
5
6
7
8
9
10

事件参数传递

  • 在执行事件函数时,有可能我们需要获取一些参数信息:比如 event 对象、其他参数

  • 情况一:获取 event 对象

    • 很多时候我们需要拿到 event 对象来做一些事情(比如阻止默认行为)
    • 假如我们用不到 this,那么直接传入函数就可以获取到 event 对象;
  • 情况二:获取更多参数

    • 有更多参数时,我们最好的方式就是传入一个箭头函数,主动执行的事件函数,并且传入相关的其他参数;
jsx
class App extends React.Component {
    constructor(props) {
        super(props);
        this.state = {}
        this.btnClick = this.btnClick.bind(this);
    }
    render() {
        return (
            <>
                <button onClick={this.btnClick}>按钮1</button>
                <button onClick={e => { this.liClick({}, 1, e)}}>按钮2</button>
            </>
        )
    }

    btnClick(event) {
        console.log("按钮发生了点击", event);
    }

    // 获取更多
    liClick(item, index, event) {
        console.log("li发生了点击", item, index, event);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

条件渲染

  • 真实开发中我们会从服务器请求到大量的数据,数据会以列表的形式存储
    • 例如 商品、购物车等等的列表
    • 在数据结构中,就是 Array 类型

如何展示列表呢?

  • 在 React 中,展示列表最多的方式就是使用数组的 map 高阶函数;
  • 还可以通过 filter、slice 对数组
jsx
class App extends React.Component {
    constructor(props) {
    super(props);
        this.state = {
            movies: ["大话西游", "海王", "流浪地球", "盗梦空间"]
        }
    }

    render() {
        return (
            <ul>
                {
                    this.state.movies.map((item, index, arr) => {
                        return (
                            <li className="item" title="li">
                                {item}
                            </li>
                        )
                    })
                }
            </ul>
        )
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

列表中的 key

Warning: Each child in List should have a unique "key" prop.

  • 这个警告是告诉我们需要在列表展示的 jsx 中添加一个 key。
  • 至于如何添加一个 key,为什么要添加一个 key?(后面探讨)

虚拟 DOM

虚拟 DOM 的创建过程

index.js

javascript
import React from './react';
import ReactDOM from './react-dom';
//let element = <h1 className="title" style={{color:'red',fontSize:'24px'}}></h1>
//let element = React.createElement('h1',{className:'title',style:{color:'red',fontSize:'50px'}},'hello');
//console.log(JSON.stringify(element));
//function Welcome(props){
//    return React.createElement('h1',{className:'title'},props.title);
//}
class Welcome extends React.Component{
    render(){
        return React.createElement('h1',{className:'title'},this.props.title);
    }
}
let element = React.createElement(Welcome,{title:'标题'});
ReactDOM.render(element, document.getElementById('root'));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

element.js

javascript
const ReactElement = function(type,props) {
    const element = {
        type: type,
        props: props,
    };
    return element;
}
function createElement(type,config,children){
  let propName;
  const props = {};
  for (propName in config) {
    props[propName] = config[propName];
  }
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    props.children = Array.prototype.slice.call(arguments,2);
  }
  return ReactElement(type,props);
}
export default createElement;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

react.js

javascript
import createElement from './element';
class Component{
    static isReactComponent = true
    constructor(props){
      this.props = props;
    }
  }
export default {
    createElement,Component
}
1
2
3
4
5
6
7
8
9
10

react-dom.js

javascript
function render(element,container){
    if(typeof element == 'string'){
      return container.appendChild(document.createTextNode(element))
    }
    let type,props;
    type = element.type;
    props = element.props;
    // 如果为true说明它是一个类组件
    if(type.isReactComponent){
      element = new type(props).render();
      type = element.type;
      props = element.props;
    }else if(typeof type =='function'){
      element = type(props);
      type = element.type;
      props = element.props;
    }
    let domElement = document.createElement(type);
    for(let propName in props){
        if(propName === 'children'){
          let children = props[propName];
          children = Array.isArray(children)?children:[children];
          children.forEach(child=>render(child,domElement));
        }else if(propName === 'className'){
          domElement.className = props[propName];
        }else if(propName === 'style'){
          let styleObj = props[propName];
          /**
          for(let attr in styleObj){
              domElement.style[attr] =  styleObj[attr];
          }
           */
          let cssText = Object.keys(styleObj).map(attr=>{
            return `${attr.replace(/([A-Z])/g,function(){ return"-"+arguments[1]})}:${styleObj[attr]}`;
          }).join(';');
          domElement.style.cssText = cssText;
        }else{
          domElement.setAttribute(propName,props[propName]);
        }
    }
    container.appendChild(domElement);
  }
export default {render};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

为什么使用虚拟 DOM

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

  • 很难跟踪状态发生的改变:原有的开发模式,我们很难跟踪到状态发生的改变,不方便针对我们应用程序进行调试;
  • 操作真实 DOM 性能较低:传统的开发模式会进行频繁的 DOM 操作,而这一的做法性能非常的低;
沪ICP备20006251号-1