React02-JSX语法

235 阅读7分钟

JSX语法

认识

const element = <h2></h2> //这就是jsx语法

看起来我们直接给一个变量赋了一个 HTML 元素,但是我们能在 JS 中直接给一个变量赋值 html 吗?是不可以的。这其实是一段 JSX 语法。

JSX:

  • 一种 JS 的语法扩展(extension),也在很多地方称之为 JS XML,因为看起来像一段 XML 。
  • 它用于描述 UI 页面,并且可以与 JS 融合一起使用
  • 不同于 Vue,我们不需要学习模块语法中的一些指令

React 选择了 JSX 而非指令:

  • React认为渲染逻辑本质上与 UI 逻辑存在内在耦合
  • 比如 UI 需要绑定事件
  • 比如某些状态发生改变,需要修改 UI
  • ALL IN JS

基本使用

书写规范

  1. JSX 的顶层只能有一个根元素
  2. 为了方便阅读,通常在 JSX的外层包裹一个小括号(),这样可以方便阅读,并且允许 JSX 换行。
  3. JSX 中单标签元素不能省略/
  4. 如果在jsx中写的HTML标签要写class,推荐替换为className,因为和js中的class重名,会报一个警告。

注释使用

{ /* 此处写注释 */ }

插入内容

JSX嵌入变量作为子元素

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

  • 当变量是 null、undefined、Boolean 类型时,内容为空,并不报错

    • 如果希望显示 null、undefined、Boolean,需要预先转成字符串(一般不会有这个需求)
    • 了解一下转换的方式:①String(变量)②变量.toString()③变量+''
  • Object 对象类型不能作为子元素

    • Object are not valid as a React Child

    • 而且我们通常也不会把一个对象直接塞进去,而是放入他的属性或者属性值,这都是允许的

      • Object.keys()[0]
      • obj.name
  • 可以插入表达式

  • 可以调用方法获取返回值

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            counter: 100,
            message: "Hello World",
            names: ["abc", "cba", "nba"],
​
            aaa: undefined,
            bbb: null,
            ccc: true,
​
            friend: { name: "kobe" },
​
            firstName: "kobe",
            lastName: "bryant",
​
            age: 20,
​
            movies: ["流浪地球", "星际穿越", "独行月球"]
        }
    }
​
    render() {
        // 1.插入标识符
        const { message, names, counter } = this.state
        const { aaa, bbb, ccc } = this.state
        const { friend } = this.state
​
        // 2.对内容进行运算后显示(插入表示)
        const { firstName, lastName } = this.state
        const fullName = firstName + " " + lastName
        const { age } = this.state
        const ageText = age >= 18 ? "成年人": "未成年人"
        const liEls = this.state.movies.map(movie => <li>{movie}</li>)
​
        // 3.返回jsx的内容
        return (
            <div>
                {/* 1.Number/String/Array直接显示出来 */}
                <h2>{counter}</h2>
                <h2>{message}</h2>
                <h2>{names}</h2>
​
                {/* 2.undefined/null/Boolean */}
                <h2>{String(aaa)}</h2>
                <h2>{bbb + ""}</h2>
                <h2>{ccc.toString()}</h2>
​
                {/* 3.Object类型不能作为子元素进行显示*/}
                <h2>{friend.name}</h2>
                <h2>{Object.keys(friend)[0]}</h2>
​
                {/* 4.可以插入对应的表达式*/}
                <h2>{10 + 20}</h2>
                <h2>{firstName + " " + lastName}</h2>
                <h2>{fullName}</h2>
​
                {/* 5.可以插入三元运算符*/}
                <h2>{ageText}</h2>
                <h2>{age >= 18 ? "成年人": "未成年人"}</h2>
​
                {/* 6.可以调用方法获取结果*/}
                <ul>{liEls}</ul>
                <ul>{this.state.movies.map(movie => <li>{movie}</li>)}</ul>
                <ul>{this.getMovieEls()}</ul>
            </div>
        )
    }
​
    getMovieEls() {
        const liEls = this.state.movies.map(movie => <li>{movie}</li>)
        return liEls
    }
}

属性绑定

  • vue中使用 v-bind:属性名="变量":属性名="变量"

JSX 中我们统一使用 {} 来绑定。

常见的需要绑定的属性:

  • img 元素的 src
  • a 元素的 href
  • 元素的 class
  • 元素的 style

基本属性的绑定比较简单,类和style属性的绑定比较复杂。style的绑定需要传入一个对象

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            title: "哈哈哈",
            imgURL: "url",
            href: "https://www.baidu.com",
​
            isActive: true,
            objStyle: {color: "red", fontSize: "30px"}
        }
    }
​
    render() {
        const { title, imgURL, href, isActive, objStyle } = this.state
​
        // 需求: isActive: true -> active
        // 1.class绑定的写法一: 字符串的拼接
        const className = `abc cba ${isActive ? 'active': ''}`
        // 2.class绑定的写法二: 将所有的class放到数组中
        const classList = ["abc", "cba"]
        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>
        )
    }
}

事件绑定

  • vue中绑定事件:v-on:click="" 或 @click=""
  • 小程序中绑定事件:bindtap=""
  • this的绑定

React 中事件命名采用小驼峰;事件传递这里提供三种方式:方式三最为推荐,同时也是最难理解的

class App extends React.Component {
    // class fields
    name = "App"
​
    constructor() {
        super()
        this.state = {
            message: "Hello World",
            counter: 100
        }
​
        this.btn1Click = this.btn1Click.bind(this)
    }
​
    btn1Click() {
        console.log("btn1Click", this);
        this.setState({ counter: this.state.counter + 1 })
    }
​
    btn2Click = () => {
        console.log("btn2Click", this)
        this.setState({ counter: 1000 })
    }
​
    btn3Click() {
        console.log("btn3Click", this);
        this.setState({ counter: 9999 })
    }
​
    render() {
        const { message } = this.state
​
        return (
            <div>
                {/* 1.this绑定方式一: bind绑定 */}
                <button onClick={this.btn1Click}>按钮1</button>
​
                {/* 2.this绑定方式二: ES6 class fields */}
                <button onClick={this.btn2Click}>按钮2</button>
​
                {/* 3.this绑定方式三: 直接传入一个箭头函数(重要) */}
                <button onClick={() => console.log("btn3Click")}>按钮3</button>
​
                <button onClick={() => this.btn3Click()}>按钮3</button>
​
                <h2>当前计数: {this.state.counter}</h2>
            </div>
        )
    }
}

很多情况下事件发生时我们需要传递一些参数:

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            message: "Hello World"
        }
    }
​
    btnClick(event, name, age) {
        console.log("btnClick:", event, this)
        console.log("name, age:", name, age)
    }
​
    render() {
        const { message } = this.state
​
        return (
            <div>
                {/* 1.event参数的传递 */}
                <button onClick={this.btnClick.bind(this)}>按钮1</button>
                <button onClick={(event) => this.btnClick(event)}>按钮2</button>
​
​
                {/* 2.额外的参数传递 */}
                <button onClick={this.btnClick.bind(this, "kobe", 30)}>按钮3(不推荐)</button>
​
                <button onClick={(event) => this.btnClick(event, "why", 18)}>按钮4</button>
            </div>
        )
    }
}

案例:Tab 栏切换

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            movies: ["星际穿越", "独行月球", "流浪低球", "星球大战"],
            currIndex: 0,
        };
    }
​
    changeCurrIndex(index) {
        this.setState({
            currIndex: index,
        });
    }
​
    render() {
        const { currIndex, movies } = this.state;
        return (
            <div>
                <ul>
                    {movies.map((item, index) => (
                        <li
                            className={currIndex === index ? "active" : ""}
                            key={item}
                            onClick={() => this.changeCurrIndex(index)}
                            >
                            {item}
                        </li>
                    ))}
                </ul>
            </div>
        );
    }
}

条件渲染

React 中三种方式来条件渲染,根据情况选择合适的即可。方式三和可选链很像

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            message: "Hello World",
​
            isReady: false,
​
            friend: undefined
        }
    }
​
    render() {
        const { isReady, friend } = this.state
​
        // 1.条件判断方式一: 使用if进行条件判断
        let showElement = null
        if (isReady) {
            showElement = <h2>准备开始比赛吧</h2>
        } else {
            showElement = <h1>请提前做好准备!</h1>
        }
​
        return (
            <div>
                {/* 1.方式一: 根据条件给变量赋值不同的内容 */}
                <div>{showElement}</div>
​
                {/* 2.方式二: 三元运算符 */}
                <div>{ isReady ? <button>开始战斗!</button>: <h3>赶紧准备</h3> }</div>
​
                {/* 3.方式三: &&逻辑与运算 */}
                {/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */}
                <div>{ friend && <div>{friend.name + " " + friend.desc}</div> }</div>
            </div>
        )
    }
}

条件渲染案例

  • 实现 v-if 效果
  • 实现 v-show 效果
class App extends React.Component {
    constructor() {
        super()
        this.state = {
            message: "Hello World",
​
            isReady: false,
​
            friend: undefined
        }
    }
​
    render() {
        const { isReady, friend } = this.state
​
        // 1.条件判断方式一: 使用if进行条件判断
        let showElement = null
        if (isReady) {
            showElement = <h2>准备开始比赛吧</h2>
        } else {
            showElement = <h1>请提前做好准备!</h1>
        }
​
        return (
            <div>
                {/* 1.方式一: 根据条件给变量赋值不同的内容 */}
                <div>{showElement}</div>
​
                {/* 2.方式二: 三元运算符 */}
                <div>{ isReady ? <button>开始战斗!</button>: <h3>赶紧准备</h3> }</div>
​
                {/* 3.方式三: &&逻辑与运算 */}
                {/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */}
                <div>{ friend && <div>{friend.name + " " + friend.desc}</div> }</div>
            </div>
        )
    }
}

列表渲染

  • vue 中 v-forv-key
  • 小程序中 wx:ifwx:key
  • 数组的高阶方法:map、filter、slice

React 中,展示列表做常用的方式就是使用数组的高阶函数 map。如果还需要对数据进行处理,filterslice 这两个高阶函数也经常使用

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            students: [
                { id: 1, name: "xiaochen", sex: "男" },
                { id: 1, name: "xiaochen", sex: "男" },
                { id: 1, name: "xiaochen", sex: "男" },
                { id: 3, name: "xiaowang", sex: "女" },
                { id: 3, name: "xiaowang", sex: "女" },
                { id: 3, name: "xiaowang", sex: "女" },
            ],
        };
    }
​
    render() {
        const { students } = this.state;
​
        return (
            <div>
                {students
                    .filter((item) => item.sex === "男")
                    .slice(0, 2)
                    .map((item) => {
                    return (
                        <div class="item" key={item.id}>
                            <div>{item.id}</div>
                            <div>{item.name}</div>
                            <div>{item.sex}</div>
                        </div>
                    );
                })}
            </div>
        );
    }
}

key的使用

key={obj.id}

原理

JSX => 虚拟DOM => 真实DOM

babel 如何解析 JSX 中的 HTML 呢?每当 遇到一个标签,就调用一次React.createElement(component, props, ...children),其中方法的参数和 vue 中的 h 函数一样。这样最终形成一颗树的结构。

  • type:当前元素的类型

    • 如果是标签元素,直接用标签名
    • 如果是组件元素,使用组件名
  • attr

    • 元素的属性及属性值,用对象形式表示
  • childrens

    • 存放在标签中的内容,以数组形式存储

React 利用 ReactElement 对象组成了一个 JS 的对象树,该对象树即虚拟 DOM,我们可以打印查看虚树的结构

class App extends React.Component {
    constructor() {
        super();
        this.state = {
            mesg: "Hello World",
        };
    }
​
    render() {
        const { mesg } = this.state;
        const virtualTree = <div>{mesg}</div>;
        console.log(virtualTree);
        return virtualTree;
    }
}

虚拟DOM作用:

  • 当页面发生改变,会生成新的虚拟 DOM,再和旧的虚拟 DOM 比较,到时候只更新 不同的地方即可

  • 通过虚拟 DOM 可以跨平台,即可生成 HTML,也可生成 Android

  • 虚拟 DOM 帮助我们从命令式编程转到了声明式编程的模式

  • React官方的说法: Virtual DOM 是一种编程理念。

    • 在这个理念中, UI 以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的 JavaScript 对象
    • 我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation)
    • 这种编程的方式赋予了React声明式的API:
    • 你只需要告诉React希望让UI是什么状态;
    • React来确保DOM和这些状态是匹配的;
    • 你不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来;

下一步:虚拟 DOM 转为真实 DOM

因此,以下两种写法完全等价

<script type="text/babel">
    // 1.定义App根组件
    class App extends React.Component {
        constructor() {
            super()
            this.state = {
                message: "Hello World"
            }
        }
​
        render() {
            const { message } = this.state
​
            return (
                <div>
                    <div className="header">Header</div>
                    <div className="Content">
                        <div>{message}</div>
                        <ul>
                            <li>列表数据1</li>
                            <li>列表数据2</li>
                            <li>列表数据3</li>
                            <li>列表数据4</li>
                            <li>列表数据5</li>
                        </ul>
                    </div>
                    <div className="footer">Footer</div>
                </div>
            )
        }
    }
​
    // 2.创建root并且渲染App组件
    const root = ReactDOM.createRoot(document.querySelector("#root"))
    root.render(<App/>)
</script>
 <script>
    // 1.定义App根组件
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          message: "Hello World"
        }
      }
​
      render() {
        const { message } = this.state
​
        const element = React.createElement(
          "div",
          null,
  /*#__PURE__*/ React.createElement(
            "div",
            {
              className: "header"
            },
            "Header"
          ),
  /*#__PURE__*/ React.createElement(
            "div",
            {
              className: "Content"
            },
    /*#__PURE__*/ React.createElement("div", null, "Banner"),
    /*#__PURE__*/ React.createElement(
              "ul",
              null,
      /*#__PURE__*/ React.createElement(
                "li",
                null,
                "\u5217\u8868\u6570\u636E1"
              ),
      /*#__PURE__*/ React.createElement(
                "li",
                null,
                "\u5217\u8868\u6570\u636E2"
              ),
      /*#__PURE__*/ React.createElement(
                "li",
                null,
                "\u5217\u8868\u6570\u636E3"
              ),
      /*#__PURE__*/ React.createElement(
                "li",
                null,
                "\u5217\u8868\u6570\u636E4"
              ),
      /*#__PURE__*/ React.createElement("li", null, "\u5217\u8868\u6570\u636E5")
            )
          ),
  /*#__PURE__*/ React.createElement(
            "div",
            {
              className: "footer"
            },
            "Footer"
          )
        );
        
        console.log(element)
​
        return element
      }
    }
​
    // 2.创建root并且渲染App组件
    const root = ReactDOM.createRoot(document.querySelector("#root"))
    root.render(React.createElement(App, null))
</script>

阶段性案例

实现一个购物车:

  • 调用 setState 后,React 会重新渲染页面

  • 注意:React不推荐直接修改原数据,比如这样

    addCount(index) {
        const { books } = this.state;
        books[index].count++;//直接修改了原来的数据
        this.setState({
            books
        });
    }
    

    应该这样

    increment(index) {
        const newBooks = [...this.state.books]
        newBooks[index].count += 1
        this.setState({ books: newBooks })
    }
    

    完整代码:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>购物车案例</title>
      <style>
        table {
          border-collapse: collapse;
          text-align: center;
        }
    ​
        thead {
          background-color: #f2f2f2;
        }
    ​
        td, th {
          padding: 10px 16px;
          border: 1px solid #aaa;
        }
      </style>
    </head>
    <body>
      
      <div id="root"></div>
    ​
      <script src="../lib/react.js"></script>
      <script src="../lib/react-dom.js"></script>
      <script src="../lib/babel.js"></script>
    ​
      <script src="./data.js"></script>
      <script src="./format.js"></script>
    ​
      <script type="text/babel">
        // 1.定义App根组件
        class App extends React.Component {
          constructor() {
            super()
            this.state = {
              books: books
            }
          }
    ​
          getTotalPrice() {
            const totalPrice = this.state.books.reduce((preValue, item) => {
              return preValue + item.count * item.price
            }, 0)
            return totalPrice
          }
    ​
          changeCount(index, count) {
            const newBooks = [...this.state.books]
            newBooks[index].count += count
            this.setState({ books: newBooks })
          }
    ​
          removeItem(index) {
            const newBooks = [...this.state.books]
            newBooks.splice(index, 1)
            this.setState({ books: newBooks })
          }
    ​
          renderBookList() {
            const { books } = this.state
    ​
            return <div>
              <table>
                <thead>
                  <tr>
                    <th>序号</th>
                    <th>书籍名称</th>
                    <th>出版日期</th>
                    <th>价格</th>
                    <th>购买数量</th>
                    <th>操作</th>
                  </tr>
                </thead>
                <tbody>
                  {
                    books.map((item, index) => {
                      return (
                        <tr key={item.id}>
                          <td>{index + 1}</td>
                          <td>{item.name}</td>
                          <td>{item.date}</td>
                          <td>{formatPrice(item.price)}</td>
                          <td>
                            <button 
                              disabled={item.count <= 1}
                              onClick={() => this.changeCount(index, -1)}
                            >
                              -
                            </button>
                            {item.count}
                            <button onClick={() => this.changeCount(index, 1)}>+</button>
                          </td>
                          <td><button onClick={() => this.removeItem(index)}>删除</button></td>
                        </tr>
                      )
                    })
                  }
                </tbody>
              </table>
              <h2>总价格: {formatPrice(this.getTotalPrice())}</h2>
            </div>
          }
    ​
          renderBookEmpty() {
            return <div><h2>购物车为空, 请添加书籍~</h2></div>
          }
    ​
          render() {
            const { books } = this.state
            return books.length ? this.renderBookList(): this.renderBookEmpty()
          }
        }
    ​
        // 2.创建root并且渲染App组件
        const root = ReactDOM.createRoot(document.querySelector("#root"))
        root.render(<App/>)
      </script></body>
    </html>