JSX语法

124 阅读8分钟

1.认识JSX语法★★★

  <!-- <script>
    // js语法中这种书写方式会直接报错
    let element = <div>林夕</div>
    console.log(element)
  </script> -->
<script type="text/babel">
// 虽然这种写法没有报错是因为babel将JSX语法进行了转化
// 1.定义App根组件
class App extends React.Component {
  constructor() {
    super()
    this.state = {
      message: "Hello World"
    }
  }
  render() {
    // 这种结构代码即包含js代码也包含js的扩展代码即成为JSX
    // 这种代码就是JSX代码
    const { message } = this.state
    const element = <h2>{message}</h2>//<h2>{message}</h2>代码本质是React.createElemnet("h2",null,"Hello World"),因为我们使用了bable
    return element
  }
}
// 2.创建root并且渲染App组件
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App/>)
// 开发react项目都要用bable进行转化
</script>

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

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

JSX是什么?

  • JSX是一种JavaScript的语法扩展(eXtension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;
  • 它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;
  • 它不同于Vue中的模块语法,不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);

还有const element = <h2>{message}</h2>又被称之为html in js;后续还有css in js;js in js;all in js

注意:没有这种写法const element ="<h2>{message}</h2>"

为什么React选择了JSX

  • React认为渲染逻辑本质上与其他UI逻辑存在内在耦合
    • 比如UI需要绑定事件(button、a原生等等);
    • 比如UI中需要展示数据状态
    • 比如在某些状态发生改变时,又需要改变UI;
  • 他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件;
  • JSX其实是嵌入到JavaScript中的一种结构语法;
  • JSX的书写规范:
    • JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素
    • 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;
    • JSX中的标签可以是单标签,也可以是双标签;
      • 注意:如果是单标签,必须以/>结尾;

2.JSX的基本使用★★★

  • jsx中的注释
    render(){
     return(
       <div>
         {/* 我是注释 */}
         <h1>{this.state.message}</h1>
         <button onClick={this.btnClick}>点击测试按钮</button>
       </div>
     )
    }
    
  • JSX嵌入变量作为子元素
    • 情况一:当变量是Number、String、Array类型时,可以直接显示
    • 情况二:当变量是null、undefined、Boolean类型时,内容为空;
      • 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;
      • 转换的方式有很多,比如toString方法、和空字符串拼接,String(变量)等方式;
    • 情况三:Object对象类型不能作为子元素(not valid as a React child)
  • JSX嵌入表达式
    • 运算表达式
    • 三元运算符
    • 执行一个函数
  <script type="text/babel">
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          message: "我是测试类组件",
          arr: ["数据1", "数据2", "数据3"],
          count: 100,
          type1: null,
          type2: undefined,
          type3: true,
          obj: {
            name: "测试"
          }
        }
      }
      render() {
        const { message, arr, count } = this.state;
        const { type1, type2, type3 } = this.state;
        const { obj } = this.state;
        const allText = message + "" + count
        return (
          <div>
            {/* 1.Number/String/Array直接显示出来 */}
            <h1>{message}</h1>
            <h1>{arr}</h1>
            <h1>{count}</h1>
            {/* 2.null、undefined、Boolean类型显示为空*/}
            <h2>{type1}</h2>
            <h2>{type2}</h2>
            <h2>{type3.toString()}</h2>
            {/* 3.Object对象类型不能作为子元素*/}
            <h2>{obj.name}</h2>
            <h2>{Object.keys(obj)[0]}</h2>
            {/* 4.可以插入对应的表达式*/}
            <h3>{message + arr}</h3>
            <h3>{allText}</h3>
            <h3>{count>100?"大于":"不大于"}</h3>
          </div>
        )
      }
    }
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(<App />);
  </script>
  • jsx绑定属性
// 1.定义App根组件
class App extends React.Component {
  constructor() {
    super()
    this.state = {
      title: "哈哈哈",
      imgURL: "",
      href: "https://www.baidu.com",
      isActive: true,
      objStyle: {color: "blue", fontSize: "24px"}
    }
  }
  render() {
    const { title, imgURL, href, isActive, objStyle } = this.state
    // 1.class绑定的写法一: 字符串的拼接
    const className = `ceshi ceshi1 ${isActive ? 'active': ''}`
    // 2.class绑定的写法二: 将所有的class放到数组中
    const classList = ["ceshi", "ceshi1"]
    if (isActive) classList.push("active")
    // 3.class绑定的写法三: 第三方库classnames -> npm install classnames
    return (
      <div>
        { /* 1.基本属性绑定 */ }
        <h2 title={title}>h2元素</h2>
        {/*<img src={imgURL} alt=""/>*/}
        <a href={href}>百度</a>
        { /* 2.绑定class属性: 使用className */ }
        <h2 className={className}>哈哈哈哈</h2>
        <h2 className={classList.join(" ")}>测试</h2>
        { /* 3.绑定style属性: 绑定对象类型 */ }
        <h2 style={{color: "red", fontSize: "30px"}}>测试</h2>
        <h2 style={objStyle}>测试</h2>
      </div>
    )
  }
}
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App/>)

3.JSX的事件绑定★★★

我们都知道在原生的Dom中进行事件绑定有两种方式:

  1. 获取DOM原生,添加监听事件
  2. 直接绑定onClick

而在React中事件监听

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。通过{}

this绑定问题

你必须谨慎对待 JSX 回调函数中的 this,在 JavaScript 中,class 的方法默认不会绑定 this。如果你忘记绑定 this.handleClick 并把它传入了 onClick,当你调用这个函数的时候 this 的值为 undefined

这并不是 React 特有的行为;这其实与 JavaScript 函数工作原理有关。通常情况下,如果你没有在方法后面添加 (),例如 onClick={this.handleClick},你应该为这个方法绑定 this

  • 原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;
  • 内部调用时,并不知道要如何绑定正确的this;

如果觉得使用 bind 很麻烦,这里有两种方式可以解决。你可以使用 public class fields 语法 正确地绑定回调函数:

class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.  
  handleClick = () => {   
       console.log('this is:', this); 
  };  
  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}

Create React App 默认启用此语法。

如果你没有使用 class fields 语法,你可以在回调中使用箭头函数

class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    // 此语法确保 `handleClick` 内的 `this` 已被绑定。    
    return (      
      <button onClick={() => this.handleClick()}>      
        Click me
      </button>
    );
  }
}

此语法问题在于每次渲染 LoggingButton 时都会创建不同的回调函数。在大多数情况下,这没什么问题,但如果该回调函数作为 prop 传入子组件时,这些组件可能会进行额外的重新渲染。我们通常建议在构造器中绑定或使用 class fields 语法来避免这类性能问题。

由此可知:

  1. bind给handleClick显示绑定this,上一节提到
  2. 使用 ES6 class fields 语法
  3. 事件监听时传入箭头函数
参数传递
class App extends React.Component {
  handleClick1(event, name, age) {
    console.log(event);
  };
  handleClick2(event, name, age) {
    // 注意测试3和测试4 
    // 测试3按钮需要绑定this,后面参数追加对照,age就变成了我们的event参数
    // 测试4是我们期望的接参数期望结果
    console.log(event);
    console.log(name, age);
  };
  render() {
    return (
      <div>
        { /* 1. event参数传递,event可以阻止默认行为 */ }
        <button onClick={this.handleClick1.bind(this)}>测试1</button>
        <button onClick={(event)=>{this.handleClick2(event)}}>测试2</button>
        { /* 2. 额外参数传参数 */ }
        <button onClick={this.handleClick2.bind(this,"林夕",18)}>测试3</button>
        <button onClick={(event)=>{this.handleClick2(event,"林夕",18)}}>测试4</button>
      </div>
    );
  }
}
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(<App />)

4.JSX的条件渲染★★★

通常你的组件会需要根据不同的情况显示不同的内容。在 React 中,你可以通过使用 JavaScript 的 if 语句、&&? : 运算符来选择性地渲染 JSX。

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      ready: false,
    }
  }
  render() {
    const { ready } = this.state
    let element = null
    if (ready) {
      element = <h2>我是方式一true</h2>
    } else {
      element = <h2>我是方式一false</h2>
    }
    return (
      <div>
        {/*方式一: 条件判断语句 */}
        {element}
        {/*方式二: 三元运算符 */}
        {ready ? <h2>方式二</h2> : <h2>方式二否则</h2>}
        {/*方式三: 与运算符&& */}
        {ready===true && <h2>方式三</h2> }
        {ready===false && <h2>方式三false</h2> }
      </div>
    )
  }
}

注意:切勿将数字放在 && 左侧: JavaScript 会自动将左侧的值转换成布尔类型以判断条件成立与否。然而,如果左侧是 0,整个表达式将变成左侧的值(0),React 此时则会渲染 0 而不是不进行渲染。

5.JSX的列表渲染★★★

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      arr: [12, 23, 4, 5],
      active: 0
    }
  }
  changeClick(active) {
    this.setState({
      active
    })
  }
  render() {
    const { arr, active } = this.state
    return (
      <div>
        {
          arr.map((item, index) => {
            return <li className={active == index ? 'active' : ''} key={item} onClick={() => this.changeClick(index)}>{item}</li>
          })
        }
      </div>
    )
  }
}

通过案例观察以及官网介绍我们可以知道

使用 filter() 筛选需要渲染的组件和使用 map() 把数组转换成组件数组。

  • 在React中,展示列表最多的方式就是使用数组的map高阶函数;

在使用列表渲染时候会出现报错因此我们需要加上key来区分

  • key主要的作用是为了提高diff算法时的效率;
  • key 值在兄弟节点之间必须是唯一的。  不过不要求全局唯一,在不同的数组中可以使用相同的 key。
  • key 值不能改变,否则就失去了使用 key 的意义!所以千万不要在渲染时动态地生成 key。

6.JSX的原理和本质★★★

实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。

  • createElement需要传递三个参数:
    • 参数一:type  当前ReactElement的类型;
      • 如果是标签元素,那么就使用字符串表示 “div”;
      • 如果是组件元素,那么就直接使用组件的名称;
    • 参数二:config
      • 所有jsx中的属性都在config中以对象的属性和值的形式存储;
      • 比如传入className作为元素的class;
    • 参数三:children
      • 存放在标签中的内容,以children数组的方式进行存储;
      • 当然,如果是多个元素呢?React内部有对它们进行处理 如下 JSX 代码:
<MyButton color="blue" shadowSize={2}>
  Click Me
</MyButton>

会编译为:

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

如果没有子节点,你还可以使用自闭合的标签形式,如:

<div className="sidebar" />

会编译为:

React.createElement(
  'div',
  {className: 'sidebar'}
)

如果你想测试一些特定的 JSX 会转换成什么样的 JavaScript,你可以尝试使用 在线的 Babel 编译器。 如果我们使用babel编译器转换之后我们复制代码就可以进行删除babel.js使用

render() {
    /*#__PURE__*/
    const element = React.createElement("div", {
      children: /*#__PURE__*/React.createElement("h2", {
        children: "\u4F60\u597D\u6D4B\u8BD5"
      })
    });
    return element
  }

jsx – ReactElement对象 – 真实DOM