二、React基础 - JSX 语法

87 阅读9分钟

一、认识JSX语法

认识JSX

// 1.定义根组件
const element = <div>Hello World</div>
// 2.渲染根组件
const root = ReactDOM.createRoot(document.querySelector("#root"))
root.render(element)
  • element变量声明赋值的标签语法是什么?
    • 不是一段字符串(无引号包裹)
    • 像HTML元素,但却写在了 js 中
    • 去掉 script 标签的 type="text/babel",会报错
    • 是jsx语法
  • JSX是什么?
    • 是一种JavaScript的语法扩展(eXtension),也称之为JavaScript XML,看起来像一段XML语法
    • 用于描述UI界面,可以于 JS 融合使用
    • 不同于Vue中的模块语法,无需专门学习模块语法中的指令(v-if、v-bind等)

为什么React选择了JSX

  • react认为渲染逻辑本质上与其他UI逻辑存在内在耦合
    • 如UI需要绑定事件(button、a链接等)
    • 如UI中需要展示数据状态
    • 如在某些状态发生改变时,又需要改变UI
  • 它们之间密不可分,所以React没有将 标记(html、css) 分离到不同的文件中,而是将其组合到了一起,是为组件(Component)
  • JSX其实是嵌入到 JS 中的一种结构语法

JSX的书写规范

  • JSX的顶层只能有一个根元素,很多时候会在外层包裹一层div元素(或Fragment)
  • 为了方便阅读,会在 jsx 外层包裹一个小括号(),并且 jsx 可以进行换行书写
  • JSX 中的标签可以是单标签,也可以是双标签
    • 如果是单标签,必须以 /> 结尾

二、JSX的基本使用

JSX的使用

  • jsx中的注释
  • JSX嵌入变量作为子元素
    • 情况一:当变量是Number、String、Array类型时,可以直接显示
      • 数组会自动拼接成字符串
    • 情况二:当变量是null、undefined、Boolean类型时,内容为空
      • 如果希望显示null、undefined、Boolean,可以转成字符串
      • toString方法、和空字符串拼接、String(变量)等方式
    • 情况三:Object对象类型不能作为子元素(not valid as a React child)
  • JSX嵌入表达式
    • 运算表达式
    • 三元运算符
    • 执行一个函数
  • JSX绑定属性
    • 如元素都会有title属性
    • 如img元素会有src属性
    • 如a元素会有href属性
    • 如元素可能需要绑定class(使用 className)
    • 如原生使用内联样式style
    <div id="root"></div>

    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>

    <script type="text/babel">
        const root = ReactDOM.createRoot(document.querySelector('#root'))
        
        class App extends React.Component {
            constructor() {
                super()

                this.state = {
                    msg: 'Hello World',
                    isActive: false,
                    bdHref: 'https://www.baidu.com',
                    arr: ['a', 'b', 'c']
                }
            }

            render() {
                const {msg, isActive, bdHref, arr} = this.state

                const className = ['aaa', 'bbb']

                if (!isActive) {
                    className.push('active')
                }
                return (
                    <div className={className.join(' ')} style={{backgroundColor: 'skyblue'}}>
                            {/* 注释 */}
                            <h2>{isActive ? 'active' : 'not active'}</h2>
                            <h2>{this.func()}</h2>
                            <a href={bdHref}>百度一下</a>
                            <h2>{arr}</h2>
                    </div>
                )
            }

            func() {
                return this.state.msg
            }
        }

        root.render(<App />)
    </script>

三、JSX的事件绑定

React事件绑定

  • 给原生DOM添加一个监听事件
    • 1⃣️、获取DOM原生,添加监听事件
    • 2⃣️、在HTML原生中,直接绑定 onclick
  • 在React中实现事件监听
    • React事件的命名采用小驼峰(camelCase),而不是纯小写
    • 需要通过 {} 传入一个事件处理函数,这个函数会在事件发生时被执行

this的四种绑定规则

  • 1.默认绑定:独立执行 foo()
  • 2.隐式绑定:被一个对象执行 obj.foo() -> obj
  • 3.显式绑定:call/apply/bind foo.call('str') -> String('str')
  • 4.new绑定:new Foo() -> 创建一个新对象,并且赋值给this

this的绑定问题

  • 事件执行之后,可能需要获取当前类的对象中相关的属性,需要用到this
    • 在绑定的事件函数中打印this,发现时undefined
const obj = {
    name: 'obj',
    foo: function() {
        console.log('foo', this)
    }
}
// obj.foo()
const config = {
    onClick: obj.foo.bind(obj)  // 这样显示绑定,click调用的时候,this指向obj
}
const click = config.onClick
click()
  • 为什么是undefined?
    • btnClcik函数不是我们主动调用的,当button发生点击时,React内部调用了btnClick
    • 它内部调用时,并不知道如何绑定正确的this
  • 解决this问题
    • 方案一:bind给btnClick显示绑定this
    • 方案二:使用ES6 class fields 语法
    • 方案三:事件监听时箭头函数(推荐)
class App extends React.Component {
    constructor() {
        super()
        this.state = {
            count: 0
        }
        this.btn1Click = this.btn1Click.bind(this)
    }
    btn1Click() {
       console.log('btn1', this);
    }
    // 箭头函数无this
    btn2Click = () => {
        console.log('btn2', this);
    }
    btn3Click = () => {
        console.log('btn3', this);
    }
    render() {
        const {msg, count} = this.state
        return (
            <div>       
                <h2>当前计数:{count}</h2>
                {/* 1.bind绑定this */}
                <button onClick={this.btn1Click}>按钮1</button>
                {/* 2.ES6 class fields */}
                <button onClick={this.btn2Click}>按钮2</button>
                {/* 2.直接传入一个箭头函数(重要) */}
                <button onClick={() => this.btn3Click()}>按钮3</button>
            </div>
        )
    }
}

事件传参传递

  • 执行事件函数时,需要获取一些参数信息,如event对象,其他参数
  • 情况一:event对象
    • 用来阻止冒泡、默认行为
    • 默认情况下,event对象直接传入,函数即可获取event对象
  • 情况二:获取更多参数
    • 最佳实践:传入一个箭头函数,主动执行的事件函数,并且传入相关的其它参数
class App extends React.Component {
    constructor() {
        super()
        this.state = {
            msg: 'Hello World'
        }
    }
    btnClick(e, name, age) {
        console.log('event', e);
        console.log('this', this);
        console.log('name', name);
        console.log('age', age);
    }
    render() {
        const { msg } = this.state
        return (
            <div>
                {/* 1.event参数的传递 */}    
                <button onClick={this.btnClick.bind(this)}>按钮1</button>
                <button onClick={(e) => this.btnClick(e)}>按钮2</button>
                {/* 2.额外的参数的传递 */}    
                {/* 第一种参数顺序改变注意 */}   
                <button onClick={this.btnClick.bind(this, '你好', 18)}>按钮3(不推荐)</button>
                {/* 推荐这种 */}   
                <button onClick={(e) => this.btnClick(e, '你好', 18)}>按钮4</button>
            </div>
        )
    }
}

列表渲染案例

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            books: ['遮天', '斗破苍穹', '完美世界', '诛仙'],
            currentIndex: 0
        }
    }
    btnClick(index) {
        this.setState({
            currentIndex: index
        })
    }
    render() {
        const {currentIndex, books} = this.state
        return (
            <div>
                <ul>
                    {books.map((i, n) => {
                        return (
                            <li 
                                className={currentIndex === n ? 'active' : ''}
                                key={i}
                                onClick={() => {
                                    this.btnClick(n)
                                }}
                                >
                                    {i}
                                </li>
                        )
                    })}    
                </ul>
            </div>
        )
    }
}

四、JSX的条件渲染

React条件渲染

  • 界面的内容会根据不同的情况展示不同的内容,可决定是否渲染某部分内容
    • vue中,通过指令控制(v-if、v-show)
    • react中,所以的条件判断都和普通的JavaScript代码一致
  • 常见条件渲染方式
    • 1⃣️、条件判断语句
      • 适合逻辑较多的情况(if...else...)
    • 2⃣️、三元运算符
      • 适合逻辑比较简单
    • 3⃣️、与运算符 &&
      • 适合如果条件成立,渲染某个组件;如果条件不成立,什么内容都不渲染
    • 4⃣️、v-show的效果
      • 主要是控制display属性是否为none

条件渲染案例

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            msg: 'Hello React',
            isShow: false
        }
    }
    btnClick() {
        this.setState({
            isShow: !this.state.isShow
        })
    }
    render() {
        const { msg, isShow } = this.state
        return (
            <div>
                <button onClick={() => this.btnClick()}>切换显示</button>
                {isShow && <h2>{msg}</h2>}
                <h2 style={{ display: isShow ? 'block' : 'none' }}>{msg}</h2>    
            </div>
        )
    }
}

五、JSX的列表渲染

React列表渲染

  • 真是开发中从服务器请求大量的数据,数据会以列表的形式存储
    • 如歌曲、歌手、排行榜列表的数据
    • 如商品、购物车、评论列表的数据
    • 如好友消息、动态、联系人列表的数据
  • 在React中并没有像Vue模版语法中的v-for指令,需要我们通过JavaScript代码的方式组织数据,转成JSX
  • 如何展示列表
    • 在React中,展示列表最多的方式就是使用数组的map高阶函数
  • 很多时候展示一个数组中的数据之前,需要对它进行一些处理
    • 过滤一些内容:filter
    • 截取数组中的一部分:slice

列表中的key

  • 不使用key会报警告
    • Each child in a list should have a unique "key" prop
  • 警告告诉我们需要在列表展示的jsx添加一个key
    • key主要作用为了提高diff算法时的效率
class App extends React.Component {
    constructor () {
        super()
        this.state = {
            books: [
                {bookName: '遮天', author: '辰东', id: 1},
                {bookName: '斗破苍穹', author: '天蚕土豆', id: 2},
                {bookName: '完美世界', author: '辰东', id: 3},
                {bookName: '大主宰', author: '天蚕土豆', id: 4},
                {bookName: '圣墟', author: '辰东', id: 5},
            ]
        }
    }
    render () {
        const { books } = this.state
        return (
            <div>
                <h2>图书列表</h2>    
                <ul>
                    {books.filter(i => i.id <= 4).slice(0, 4).map(i => {
                        return (
                            <li key={i.id}>{i.bookName}</li>
                        )
                    })}    
                </ul>
            </div>
        )
    }
}

六、JSX的原理和本质

JSX的本质

  • jsx仅仅只是 React.createElement(Component, props, ...children)函数的语法糖
    • 所有的jsx最终都会被转换成 React.createElement 的函数调用
  • createElement需要传递三个参数
    • 参数一:type
      • 当前 ReactElement 的类型
      • 如果是标签元素,那么就使用字符串表示 "div"
      • 如果是组件元素,那么就直接使用组件的名称
    • 参数二:config
      • 所有jsx中的属性都在config中以对象的属性和值的形式存储
      • 比如传入className作为元素的class
    • 参数三:children
      • 存放在标签中的内容,以children数组的方式进行存储
      • 如果是多个元素,React内部对它们进行处理

createElement 源码

  • package/react/src/ReactElements.js/createElement

Babel 官网查看

  • 默认jsx是通过babel帮我们进行语法转换的,直接写jsx代码都需要依赖babel
  • babel官网中快速查看转换过程

直接编写jsx代码

  • 自己编写React.createElement代码
    • 界面依旧正常渲染
    • 不需要引入babel
      • type="text/babel"可以删除
<div id="root"></div>

<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>

<script>
    class App extends React.Component {
        constructor () {
            super()
            this.state = {

            }
        }

        render() {
            const ele = /*#__PURE__*/React.createElement("div", {
                    class: "container",
                    title: "Hello",
                    children: [/*#__PURE__*/React.createElement("h2", {
                        children: "Hello World"
                    }), /*#__PURE__*/React.createElement("div", {
                        class: "content",
                        children: /*#__PURE__*/React.createElement("ul", {
                        children: [/*#__PURE__*/React.createElement("li", {
                            children: "1"
                        }), /*#__PURE__*/React.createElement("li", {
                            children: "2"
                        }), /*#__PURE__*/React.createElement("li", {
                            children: "3"
                        })]
                        })
                    })]
                    });
            return ele
        }
    }

    const root = ReactDOM.createRoot(document.querySelector('#root'))
    root.render(React.createElement(App))
</script>

虚拟DOM的创建过程

  • 通过React.createElement最终创建出来一个 ReactElement 对象
  • React.Element对象作用?为什么需要?
    • React利用React.Element对象组成了一个JavaScript对象树
    • JavaScript对象树就是虚拟DOM(Virtual DOM)
  • 如何查看React.Element的树结构?
    • 在jsx返回结果进行打印
  • ReactElement最终形成的树结构就是Virtual DOM

jsx - 虚拟DOM - 真实DOM

jsx代码 => ReactElement对象 => 真实DOM

声明式编程

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

购物车案例

  • 在界面上以表格的形式,显示一些书籍的数据
  • 在底部显示书籍的总价格
  • 点击 + 或 - 可以增加或减少书籍数量(如果为1,那么不能继续)
  • 点击移除按钮,可以将书籍移除(当所有的书籍移除完毕时,显示:购物车为空)
书籍名称出版日期价格购买数量操作
1《算法导论》2006-9¥85.00-1+移除
2《UNIX编程艺术》2006-2¥59.00-1+移除
3《编程珠玑》2008-10¥39.00-1+移除
4《代码大全》2006-3¥128.00-1+移除

总价格:¥311

书籍数据

const books = [
    {
        bookName: '《算法导论》',
        public: '2006-9',
        price: '49.00',
        count: 1,
        id: 1
    },
    {
        bookName: '《UNIX编程艺术》',
        public: '2006-2',
        price: '59.00',
        count: 1,
        id: 2
    },
    {
        bookName: '《编程珠玑》',
        public: '2008-10',
        price: '39.00',
        count: 1,
        id: 3
    },
    {
        bookName: '《代码大全》',
        public: '2006-3',
        price: '99.00',
        count: 1,
        id: 4
    },
]

代码

class App extends React.Component {
    constructor() {
        super()
        this.state = {
            books: books
        }
    }
    increment(index) {
        const { books } = this.state
        const newBooks = [...books]
        newBooks[index].count++
        this.setState({
            books: newBooks
        })
    }
    decrement(index) {
        const { books } = this.state
        const newBooks = [...books]
        newBooks[index].count--
        this.setState({
            books: newBooks
        })
    }
    removeItem(index) {
        const { books } = this.state
        const newBooks = [...books]
        newBooks.splice(index, 1)
        this.setState({
            books: newBooks
        })
    }
    render() {
        const { books } = this.state

        const totalPrice = books.reduce((pre, item) => {
            return pre + item.price * item.count
        }, 0)

        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.bookName}</td>    
                                        <td>{item.public}</td>    
                                        <td>{item.price}</td>    
                                        <td>
                                            <button 
                                                disabled={item.count <= 0}
                                                onClick={() => this.decrement(index)}
                                            >
                                                -
                                            </button>    
                                            {item.count}
                                            <button onClick={() => this.increment(index)}>+</button>    
                                        </td>
                                        <td>
                                            <button onClick={() => this.removeItem(index)}>
                                                移除    
                                            </button>    
                                        </td>    
                                    </tr>
                                )
                            })
                        }
                    </tbody>
                </table>
                <div>总价格:{'¥' + totalPrice}</div>
                {books.length === 0 && <h1>请选择您的书籍</h1>}
            </div>
        )
    }
}