第二节:jsx语法深度剖析和jsx本质的探究

一. jsx说明及用法

1. 什么jsx?

  JSX是一种JavaScript的语法扩展(extension),也在很多地方称之为JavaScript XML,因为看起就是一段XML语法;

  它用于描述我们的UI界面,并且其完成可以和JavaScript融合在一起使用;

  它不同于Vue中的模块语法,你不需要专门学习模块语法中的一些指令(比如v-for、v-if、v-else、v-bind);

 

2. 为什么React选择使用jsx?

(1) React认为渲染逻辑本质上与其他UI逻辑存在内在耦合

  比如UI需要绑定事件(button、a原生等等)

  比如UI中需要展示数据状态

  比如在某些状态发生改变时,又需要改变UI

(2) 他们之间是密不可分,所以React没有将标记分离到不同的文件中,而是将它们组合到了一起,这个地方就是组件(Component)

 

3. JSX的书写规范

(1) JSX的顶层只能有一个根元素,所以我们很多时候会在外层包裹一个div元素(或者使用后面书写Fragment,可以包裹多个根元素)

(2) 为了方便阅读,我们通常在jsx的外层包裹一个小括号(),这样可以方便阅读,并且jsx可以进行换行书写;

(3) JSX中的标签可以是单标签,也可以是双标签;

  💝 注意:如果是单标签,必须以 /> 结尾;

(4) 使用 {xxx} 符号,渲染变量、方法等  PS:类似于vue的 {{xxxx}}  

(5) 注释的写法:{/* 我是注释内容 */}

         render() {
                let { msg } = this.state;
                return (
                    <div>
                        {/* 我是注释 */}
                        <h2>{msg}</h2>
                    </div>
                );
            }

 

4. JSX中插入变量

情况一:

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

情况二:

  当变量是null、undefined、Boolean类型时,内容为空(即:什么也不显示)

  ✓ 如果希望可以显示null、undefined、Boolean,那么需要转成字符串;

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

情况三:

  Object对象类型不能作为子元素(🎉 报错:Objects are not valid as a React child !!!) 

           // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: 'hello ypf',
                    myName: 18,
                    myHobbies: ['购物', '上网', '打游戏'],
                    myOthers: [null, undefined, true],
                    myObj: { name: 'lmr', age: 19 }
                }
            }
            // 1.2 渲染方法
            render() {
                let { msg, myName, myHobbies, myOthers, myObj } = this.state;
                return (
                    <div>
                        { /* 1 string类型 */}
                        <h2>{msg}</h2>
                        { /* 2 number类型 */}
                        <h2>{myName}</h2>
                        { /* 3 string类型 */}
                        <h2>{myHobbies}</h2>
                        { /* 4 null、undefined、Boolean */}
                        { /* 4.1 默认不显示 */}
                        <h2>{myOthers[0]},{myOthers[1]},{myOthers[2]}</h2>
                        { /* 4.2 转换成string才能显示 */}
                        <h2>{myOthers[0] + ""},{String(myOthers[1])},{myOthers[2].toString()}</h2>
                        { /* 5 Obejct类型报错 */}
                        { /* <h2>{myObj}</h2> */}
                        { /* 6 三元运算符 */}
                        <h2>{myName >= 18 ? '成年人' : '未成年人'}</h2>
                        { /* 7 函数调用 */}
                        <h2>{this.getMsg()}</h2>
                    </div>
                );
            }
            // 普通函数
            getMsg() {
                return '我是一个普通函数'
            }

 

5. JSX嵌入表达式

  运算表达式、三元运算符、执行一个函数

 详见上述代码

 

6. 属性绑定

 (1). 基本属性

   比如元素都会有title属性、img元素会有src属性、a元素会有href属性

   用法: <h1 xxx={}><h1/>

 (2). class绑定

   用法:<h2 className={}></h2>

   A. 直接字符串拼接

   B. 放到数组里,然后使用join方法转换成空格分隔的字符串

 (3). style绑定

   用法: <h2 style={{color: "red", fontSize: "30px"}}>呵呵呵呵</h2>

              <h2 style={styleObj}>呵呵呵呵</h2>

💝 PS: 绑定的内容是一个对象!! 上述第一个h2标签, 第二个{}, 代表的是对象。

 (4). 自定义多个属性的绑定

    可以使用 <h2 {...obj}></h2> 写法,进行一次性绑定多个属性

查看代码

  class App extends React.Component {
            // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: '基本属性绑定',
                    myTitle: 'hhh',
                    imgUrl: 'https://www.baidu.com/img/bdlogo.png',
                    myHref: 'https://www.baidu.com'
                }
            }
            // 1.2 渲染方法
            render() {
                let { msg, myTitle, imgUrl, myHref } = this.state;

                // 2. 属性绑定
                // 直接字符串
                let class1 = "abc def hgk";
                let classList = ['abc', 'def', 'hgk']

                // 3. style绑定
                let styleObj = { color: "red", fontSize: "30px" };

                // 4. 自定义多个属性的绑定
                let myObj = { name: "ypf", age: 18, school: "gx" }

                return (
                    <div>
                        { /* 1. 基本属性 */}
                        <h2 title={myTitle}>{msg}</h2>
                        <img src={imgUrl} alt="" />
                        <a href={myHref}>点我跳转</a>

                        { /* 2. class属性 */}
                        <h2 className={class1}>class属性绑定1</h2>
                        <h2 className={classList.join(" ")}>class属性绑定2</h2>

                        { /* 3. style属性 */}
                        <h2 style={{ color: "red", fontSize: "30px" }}>style属性绑定1</h2>
                        <h2 style={styleObj}>style属性绑定2</h2>

                        { /* 4. 自定义多个属性 */}
                        <h2 {...myObj}>自定义绑定多个属性</h2>

                    </div>
                );
            }
        }

 

 

二. jsx核心用法

1. 事件绑定

(1). 用法

  React 事件的命名采用 "小驼峰式"(camelCase),而不是纯小写;我们需要通过{}传入一个事件处理函数,这个函数会在事件发生时被执行

  比如: <h2 onClick={xxxx}>xxx<h2/>

(2).  this的绑定问题

  默认在onClick中直接调用一个方法,方法中的this是undefined,为什么呢?

  (1). 普通模式下:默认绑定,this指向windows

  (2). Bable转换,即严格模式下,this则为undefined

  react中如果直接 onClick={this.btnclick}, 原因是btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数;而它内部调用时,并不知道要如何绑定正确的this;

  详见 默认情况和babel的案例  以及 默认按钮1

查看代码
     <script>
        // 测试this的问题--普通模式下
        {
            console.log('-------------测试this的问题--普通模式下-----------------');
            const obj = {
                name: 'ypf',
                foo: function () {
                    console.log("foo:", this);
                }
            }
            obj.foo();       //这里的this指向obj
            const config = {
                onClick: obj.foo
            }
            const click = config.onClick;
            click();  //this为windows

        }
    </script>

<script type="text/babel">
        // 测试this的问题--严格模式下
        {
            console.log('-------------测试this的问题--严格模式下-----------------');
            const obj = {
                name: 'ypf',
                foo: function () {
                    console.log("foo:", this);
                }
            }
            obj.foo();       //这里的this指向obj
            const config = {
                onClick: obj.foo
            }
            const click = config.onClick;
            click();  //this为undefined, 虽然是默认调用,但是babel会转换成严格模式,严格模式下的this就是undefined
        }
    </script>

(3).  解决this的3种方案

方案1:使用bind显示的绑定this,有下面两种写法

 (1). 每次调用的时候绑定,eg:  onClick={this.btnClick.bind(this)}

 (2). 在constructor中提前绑定,  eg: this.btnClick=this.btnClick.bind(this)

方案2:使用 ES6 class fields 语法

  借助类中的field字段,将方法声明成箭头函数,由于箭头函数中没有this,则去外层作用域中找,外层作用域的this就是app实例,正好符合使用

  btnClick4 = () =>{}

方案3:事件监听时传入箭头函数 【强烈推荐】

  onClick={() => this.btnClick1()}

  直接传入一个箭头函数,this则去外层作用域找,即render函数中的this,正好符合使用 

查看代码
 <script type="text/babel">
        // 1. 定义跟组件
        class App extends React.Component {
            // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: 'this绑定的几种情况'
                }
                // 提前绑定
                this.btnClick3 = this.btnClick3.bind(this);
            }
            //实例方法
            btnClick1() {
                console.log('----------btnClick1---------')
                console.log('btnClick1---this:', this)
            }
            btnClick2() {
                console.log('----------btnClick2---------')
                console.log('btnClick2---this:', this)
            }
            btnClick3() {
                console.log('----------btnClick3---------')
                console.log('btnClick3---this:', this)
            }
            // ES6中的Class-field
            btnClick4 = () => {
                console.log('----------btnClick4---------')
                console.log('btnClick4---this:', this)
            }
            // 1.2 渲染方法
            render() {
                let { msg } = this.state;
                return (
                    <div>
                        <h2>{msg}</h2>
                        <p><button onClick={this.btnClick1}>1.默认</button></p>
                        {/* 方案1:bind绑定 */}
                        <p><button onClick={this.btnClick2.bind(this)}>2-1.每次调用bind</button></p>
                        <p><button onClick={this.btnClick3}>2-2.提前bind</button></p>
                        {/* 方案2:ES6中的Class-field */}
                        <p><button onClick={this.btnClick4}>3.ES6中的Class-field</button></p>

                        {/* 方案3:事件监听时候,直接传入一个箭头函数 */}
                        <p><button onClick={() => this.btnClick1()}>4.ES6中的Class-field</button></p>
                    </div>
                );
            }
        }
        // 2. 创建root并渲染App组件
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        root.render(<App />)

    </script>

 

2. 参数传递

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

(1).  event对象默认传递

 方案1: 箭头函数的模式,event需要显式传递,可以传递到任何位置

 方案2: 使用this.btnClick.bind(this)的模式,event默认传递,默认在最后一个参数的位置

(2).  额外参数的传递

 方案1:箭头函数的模式,因为可以随意传递参数,非常简单,所见即所得,event对象想传递在哪都可以  【推荐】

 方案2:使用this.btnClick.bind(this,p1,p2)的模式,event默认传递在最后一个参数的位置,这就要求btnClick的接收参数需要最后一个是event 【不推荐】 

查看代码
 <script type="text/babel">
        // 1. 定义跟组件
        class App extends React.Component {
            // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: 'hello ypf'
                }
            }
            // 实例方法
            btnClick(event, name, age) {
                console.log('--------------btnClick------------------');
                console.log('btnClick--this:', this);
                console.log('event:', event);
                console.log('name:', name);
                console.log('age:', age);
            }

            // 1.2 渲染方法
            render() {
                let { msg } = this.state;
                return (
                    <div>
                        <h2>{msg}</h2>
                        {/* 1.event参数的传递 */}
                        <p><button onClick={this.btnClick.bind(this)}>参数传递1</button></p>
                        <p><button onClick={(event) => this.btnClick(event)}>参数传递2</button></p>

                        {/* 2.额外参数的传递 */}
                        {/* 2.1 推荐写法 */}
                        <p><button onClick={(event) => this.btnClick(event, 'ypf', 18)}>额外参数传递1</button></p>
                        {/* 2.2 不推荐写法,因为event默认会放在最后一个参数上传递
                            点击按钮的结果: 
                                event:'ypf'
                                name: 18
                                age: event对象了                     
                        */}
                        <p><button onClick={this.btnClick.bind(this, 'ypf', 18)}>额外参数传递2</button></p>
                    </div>
                );
            }
        }

 

3. 条件渲染

 某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容,在vue中通过 v-if 或 v-show来处理,而在react中,所有的条件判断都和普通的JavaScript代码一致;

方式一:条件判断语句

  适合逻辑较多的情况

方式二:三元运算符

  适合逻辑比较简单

方式三:与运算符&&

  适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染;

方式四: v-show的效果

 主要是控制display属性为:none 还是 display 

查看代码
 // 1. 定义跟组件
        class App extends React.Component {
            // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: 'hello ypf',
                    isReady: false,
                    friend: undefined,
                    isShow: true
                }
            }
            // 实例方法
            myChange() {
                this.setState({
                    isShow: !this.state.isShow
                })
            }
            // 1.2 渲染方法
            render() {
                let { msg, isReady, friend, isShow } = this.state;
                // 方式1:直接使用if判断
                let showElement = null;
                if (isReady) {
                    showElement = <h2>你好啊1</h2 >;
                } else {
                    showElement = <h2>你好啊2</h2 >;
                }
                return (
                    <div>
                        <h2>{msg}</h2>
                        {/* 方式1: 根据条件给变量赋值不同的内容 */}
                        <div>{showElement}</div>
                        {/* 方式2: 三元运算符 */}
                        <div>{isReady ? <h2>你好啊1</h2 > : <h2>你好啊2</h2 >}</div>
                        {/* 方式3: &&逻辑与运算 */}
                        {/* 场景: 当某一个值, 有可能为undefined时, 使用&&进行条件判断 */}
                        <div>{friend && <span>{friend.name + "" + friend.age}</span>}</div>
                        {/* 方式4: 模拟v-show的效果 , 下面的第二个{},代表是一个对象啊*/}
                        <div><button onClick={() => this.myChange()}>切换</button></div>
                        <div style={{ display: isShow ? 'block' : 'none' }}>{msg}</div>
                    </div>
                );
            }
        }

 

4. 小案例

A 事件绑定--点击变色案例

(1).核心思路

  点击的时候设置currentIndex等于当前的index,然后class中通过判断currentIndex是否等于index,决定是否添加red属性

(2).分享三种写法

  A. 全部写到return里

  B. 将liElement抽离出来

  C. 将map的回调抽离出来

详见代码

<!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>
        ul>li {
            margin-bottom: 5px;
            cursor: pointer;
        }

        .red {
            color: red;
        }
    </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 type="text/babel">
        // 1. 定义跟组件
        class App extends React.Component {
            // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: '点击变色案例',
                    movies: ["星际穿越", "盗梦空间", "大话西游", "流浪地球"],
                    currentIndex: -1
                }
            }
            //点击变色方法
            myClick(index) {
                this.setState({ currentIndex: index })
            }
            // 1.2 渲染方法
            render() {
                let { msg, movies, currentIndex } = this.state;

                //写法1: 全部写在return里
                /*        {
                           return (
                               <div>
                                   <h2>{msg}</h2>
                                   <ul>
                                       {movies.map((item, index) =>
                                       (
                                           <li
                                               key={index}
                                               onClick={() => this.myClick(index)}
                                               className={index == currentIndex ? "red" : ""}
                                           >{item}</li>
                                       )
                                       )}
                                   </ul>
                               </div>
                           );
                       } 
                */

                // 写法2:抽离出来整个li列表
                /*       {
                          let liElement = movies.map((item, index) =>
                          (
                              <li
                                  key={index}
                                  onClick={() => this.myClick(index)}
                                  className={index == currentIndex ? "red" : ""}
                              >{item}</li>
                          ))
                          return (
                              <div>
                                  <h2>{msg}</h2>
                                  <ul>
                                      {liElement}
                                  </ul>
                              </div>
                          );
                      } */


                // 写法3:将map的回调抽离出来
                {
                    let liElementFunc = (item, index) =>
                    (
                        <li
                            key={index}
                            onClick={() => this.myClick(index)}
                            className={index == currentIndex ? "red" : ""}
                        >{item}</li>
                    );
                    return (
                        <div>
                            <h2>{msg}</h2>
                            <ul>
                                {movies.map(liElementFunc)}
                            </ul>
                        </div>
                    );
                }

            }
        }
        // 2. 创建root并渲染App组件
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        root.render(<App />)

    </script>
</body>

</html>
View Code

 

B 条件渲染列表

可以分步编写,也可以一次性链式编程直接在return里写完,主要涉及到几个高阶函数。

  filter:用于过滤

  slice:是一个纯函数,且会产生一个新的数组,不会对原数组产生影响。 范围是前闭后开:[start,end)

  map:遍历元素,组合成一个新的数组返回,新的数组中的内容可以自行添加,不仅仅局限于遍历对象中数据。

 ps:补充一个小细节, 如果加上{}, 就需要加return了

     list.map(item=>(<h1>{xxx}</h1>))

    list.map(item=>{return (<h1>{xxx}</h1>)})

 代码分享:

<!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>React模板</title>
    <style>
        .myDiv {
            width: 200px;
            height: 150px;
            border: 1px solid black;
            margin: 5px;
            padding: 0 10px;
        }
    </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 type="text/babel">
        // 1. 定义跟组件
        class App extends React.Component {
            // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: '下面是符合条件的学生信息',
                    studentList: [
                        { id: 111, name: "ypf1", score: 100 },
                        { id: 222, name: "ypf2", score: 101 },
                        { id: 333, name: "ypf3", score: 102 },
                        { id: 444, name: "ypf4", score: 103 },
                        { id: 555, name: "ypf5", score: 104 },
                    ]

                }
            }
            // 1.2 渲染方法
            render() {
                let { msg, studentList } = this.state;
                // 需求:求分数大于100分的任意两个学生的信息
                // 写法1--逐条业务来写
                {
                    let stu1 = studentList.filter(item => item.score > 100);
                    let stu2 = stu1.slice(0, 2);  // 范围是 [start,end), 前闭后开
                    let stuElement = stu2.map(item =>
                    (
                        <div key={item.id} className="myDiv">
                            <p>编号:{item.id}</p>
                            <p>姓名:{item.name}</p>
                            <p>分数:{item.score}</p>
                        </div>
                    )
                    );
                    return (
                        <div>
                            <h2>{msg}</h2>
                            {stuElement}
                        </div>
                    );
                }


                // 写法2--直接写到return里, 一步到位,链式编程
                /*    {
                       return (
                           <div>
                               <h2>{msg}</h2>
                               {studentList.filter(item => item.score > 100).slice(0, 2).map(item => (
                                   <div key={item.id} className="myDiv">
                                       <p>编号:{item.id}</p>
                                       <p>姓名:{item.name}</p>
                                       <p>分数:{item.score}</p>
                                   </div>
                               ))}
                           </div>
                       );
                   } */

            }
        }
        // 2. 创建root并渲染App组件
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        root.render(<App />)

    </script>
</body>

</html>
View Code

 

三. 综合案例--购物车

1 修改数据常见写法

 (1). 先把要求改的数据浅拷贝一份

 (2). 对浅拷贝的这个数据进行修改

 (3). 调用setState方法,用修改后的数据进行覆盖

2. 核心功能

  增加:可以根据id 或者 index直接查找到数据,然后修改,然后赋值。

  减少:同增加

  删除:可以利用filter过滤,或者利用 splice方法对原数组进行删除。

  PS: splice(start,deleteCount), 表示从start的位置开始,删除deleteCount个元素

   splice的各种用法,详见:https://www.cnblogs.com/yaopengfei/p/15870038.html

  求和:使用reduce直接求和

  显示:根据有无数据,封装两个方法 renderBookList 和 renderBookEmpty

版本1代码 

<!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>购物车案例-版本1</title>
    <style>
        table {
            border-collapse: collapse;
            text-align: center;
        }

        thead {
            background-color: #f2f2f2;
        }

        td,
        th {
            padding: 10px 16px;
            border: 1px solid #aaa;
        }

        .btn1 {
            margin-right: 5px;
        }

        .btn2 {
            margin-left: 5px;
        }
    </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 type="text/babel">
        // 1. 定义跟组件
        class App extends React.Component {
            // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: 'hello ypf',
                    bookList: books
                }
            }
            // 实例方法
            // 增加
            myAdd(id) {
                // 通常在react中,不推荐直接去修改原先那个this.state.books, 而是先做一个浅拷贝,然后修改这个数据,最后调用setState赋值给它并渲染
                let tempBookList = [...this.state.bookList];  //浅拷贝
                tempBookList.forEach(item => {
                    if (item.id === id) item.count++
                });
                // 显式的调用setState函数,会自动去调用下面的render函数
                this.setState({ bookList: tempBookList })
            }
            // 减少
            myReduce(id) {
                let tempBookList = [...this.state.bookList];  //浅拷贝
                for (const item of tempBookList) {
                    if (item.id === id) item.count--
                }
                // 修改渲染
                this.setState({ bookList: tempBookList })
            }
            // 删除
            myDelete(id) {
                this.setState({
                    bookList: this.state.bookList.filter(item => item.id != id)
                })
            }
            // 总和
            myTotalPrice() {
                let total = this.state.bookList.reduce((preValue, currentValue) => {
                    return preValue + currentValue.price * currentValue.count;
                }, 0);
                return total;
            }
            // 1.2 渲染方法
            render() {
                let { msg, bookList } = this.state;
                return (
                    <div>
                        <table>
                            <thead>
                                <tr>
                                    <th>序号</th>
                                    <th>书籍名称</th>
                                    <th>出版日期</th>
                                    <th>价格</th>
                                    <th>购买数量</th>
                                    <th>操作</th>
                                </tr>
                            </thead>
                            <tbody>
                                {
                                    bookList.map((item, index) => (
                                        <tr key={item.id}>
                                            <td>{item.id}</td>
                                            <td>{item.name}</td>
                                            <td>{item.date}</td>
                                            <td>¥{item.price}</td>
                                            <td>
                                                <button className="btn1" onClick={() => this.myReduce(item.id)} disabled={item.count <= 0}>-</button>
                                                {item.count}
                                                <button className="btn2" onClick={() => this.myAdd(item.id)}>+</button>
                                            </td>
                                            <td><button onClick={() => this.myDelete(item.id)}>删除</button></td>
                                        </tr>
                                    ))
                                }

                            </tbody>
                        </table>
                        <h2>总价格:${this.myTotalPrice()}</h2>
                    </div>
                );
            }
        }
        // 2. 创建root并渲染App组件
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        root.render(<App />)

    </script>
</body>

</html>
View Code

版本2代码 

<!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>购物车案例-版本1</title>
    <style>
        table {
            border-collapse: collapse;
            text-align: center;
        }

        thead {
            background-color: #f2f2f2;
        }

        td,
        th {
            padding: 10px 16px;
            border: 1px solid #aaa;
        }

        .btn1 {
            margin-right: 5px;
        }

        .btn2 {
            margin-left: 5px;
        }
    </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 type="text/babel">
        // 1. 定义跟组件
        class App extends React.Component {
            // 1.1 构造函数
            constructor() {
                super();
                this.state = {
                    msg: 'hello ypf',
                    bookList: books
                }
            }
            // 实例方法
            // 增加
            myAdd(id) {
                // 通常在react中,不推荐直接去修改原先那个this.state.books, 而是先做一个浅拷贝,然后修改这个数据,最后调用setState赋值给它并渲染
                let tempBookList = [...this.state.bookList];  //浅拷贝
                tempBookList.forEach(item => {
                    if (item.id === id) item.count++
                });
                // 显式的调用setState函数,会自动去调用下面的render函数
                this.setState({ bookList: tempBookList })
            }
            // 减少
            myReduce(id) {
                let tempBookList = [...this.state.bookList];  //浅拷贝
                for (const item of tempBookList) {
                    if (item.id === id) item.count--
                }
                // 修改渲染
                this.setState({ bookList: tempBookList })
            }
            // 删除
            myDelete(id) {
                this.setState({
                    bookList: this.state.bookList.filter(item => item.id != id)
                })
            }
            // 总和
            myTotalPrice() {
                let total = this.state.bookList.reduce((preValue, currentValue) => {
                    return preValue + currentValue.price * currentValue.count;
                }, 0);
                return total;
            }
            // 1.2 渲染方法
            render() {
                let { msg, bookList } = this.state;
                return (
                    <div>
                        <table>
                            <thead>
                                <tr>
                                    <th>序号</th>
                                    <th>书籍名称</th>
                                    <th>出版日期</th>
                                    <th>价格</th>
                                    <th>购买数量</th>
                                    <th>操作</th>
                                </tr>
                            </thead>
                            <tbody>
                                {
                                    bookList.map((item, index) => (
                                        <tr key={item.id}>
                                            <td>{item.id}</td>
                                            <td>{item.name}</td>
                                            <td>{item.date}</td>
                                            <td>¥{item.price}</td>
                                            <td>
                                                <button className="btn1" onClick={() => this.myReduce(item.id)} disabled={item.count <= 0}>-</button>
                                                {item.count}
                                                <button className="btn2" onClick={() => this.myAdd(item.id)}>+</button>
                                            </td>
                                            <td><button onClick={() => this.myDelete(item.id)}>删除</button></td>
                                        </tr>
                                    ))
                                }

                            </tbody>
                        </table>
                        <h2>总价格:${this.myTotalPrice()}</h2>
                    </div>
                );
            }
        }
        // 2. 创建root并渲染App组件
        const root = ReactDOM.createRoot(document.querySelector("#root"));
        root.render(<App />)

    </script>
</body>

</html>
View Code

 

四. jsx原理 

1. jsx的本质

(1). 实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。     所有的jsx最终都会被转换成React.createElement的函数调用。

(2). createElement需要传递三个参数:

◼ 参数一:type

  当前ReactElement的类型;

  如果是标签元素,那么就使用字符串表示 “div”; 如果是组件元素,那么就直接使用组件的名称;

◼ 参数二:config

  所有jsx中的属性都在config中以对象的属性和值的形式存储; 比如传入className作为元素的class;

◼ 参数三:children

  存放在标签中的内容,以children数组的方式进行存储;

  当然,如果是多个元素呢?React内部有对它们进行处理,处理的源码在下方

(3).可以在babel的官网中快速查看转换的过程:https://babeljs.io/repl/#?presets=react

  转换代码详见截图

 2. 自己来编写React.createElement代码

   我们就没有通过jsx来书写了,界面依然是可以正常的渲染。 另外,在这样的情况下,你还需要babel相关的内容吗?不需要了

 ✓ 所以,type="text/babel"可以被我们删除掉了;

 ✓ 所以,<script src="../react/babel.min.js"></script>可以被我们删除掉了;

 改造代码:将上述babel转换的代码 /*#__PURE__*/_jsxs  改成:  react.createElement

 代码分享:

<!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>
</head>

<body>

    <div id="root"></div>


    <!-- 引入三个必备库 -->
    <script src="../lib/react.js"></script>
    <script src="../lib/react-dom.js"></script>


    <script>
        // 1.定义App根组件
        class App extends React.Component {
            constructor() {
                super()
                this.state = {
                    message: "Hello World"
                }
            }

            render() {
                const { message } = this.state


                // 用的是原生的createElement ,所以根本不需要babel
                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>

</body>

</html>
View Code

 

3. 虚拟Dom创建过程

(1). 我们通过 React.createElement 最终创建出来一个 ReactElement对象:

(2). 这个ReactElement对象是什么作用呢?React为什么要创建它呢?

   原因是React利用ReactElement对象组成了一个JavaScript的对象树, JavaScript的对象树就是虚拟DOM(Virtual DOM)

(3).如何查看ReactElement的树结构呢?

   我们可以将之前的jsx返回结果进行打印; 注意下面代码中我打jsx的打印;

(4). 而ReactElement最终形成的树结构就是Virtual DOM 

 

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
    <!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>
            ul>li {
                margin-bottom: 5px;
                cursor: pointer;
            }
    
            .red {
                color: red;
            }
        </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 type="text/babel">
            // 1. 定义跟组件
            class App extends React.Component {
                // 1.1 构造函数
                constructor() {
                    super();
                    this.state = {
                        msg: '点击变色案例',
                        movies: ["星际穿越", "盗梦空间", "大话西游", "流浪地球"],
                        currentIndex: -1
                    }
                }
                //点击变色方法
                myClick(index) {
                    this.setState({ currentIndex: index })
                }
                // 1.2 渲染方法
                render() {
                    let { msg, movies, currentIndex } = this.state;
    
                    //写法1: 全部写在return里
                    /*        {
                               return (
                                   <div>
                                       <h2>{msg}</h2>
                                       <ul>
                                           {movies.map((item, index) =>
                                           (
                                               <li
                                                   key={index}
                                                   onClick={() => this.myClick(index)}
                                                   className={index == currentIndex ? "red" : ""}
                                               >{item}</li>
                                           )
                                           )}
                                       </ul>
                                   </div>
                               );
                           } 
                    */
    
                    // 写法2:抽离出来整个li列表
                    /*       {
                              let liElement = movies.map((item, index) =>
                              (
                                  <li
                                      key={index}
                                      onClick={() => this.myClick(index)}
                                      className={index == currentIndex ? "red" : ""}
                                  >{item}</li>
                              ))
                              return (
                                  <div>
                                      <h2>{msg}</h2>
                                      <ul>
                                          {liElement}
                                      </ul>
                                  </div>
                              );
                          } */
    
    
                    // 写法3:将map的回调抽离出来
                    {
                        let liElementFunc = (item, index) =>
                        (
                            <li
                                key={index}
                                onClick={() => this.myClick(index)}
                                className={index == currentIndex ? "red" : ""}
                            >{item}</li>
                        );
                        return (
                            <div>
                                <h2>{msg}</h2>
                                <ul>
                                    {movies.map(liElementFunc)}
                                </ul>
                            </div>
                        );
                    }
    
                }
            }
            // 2. 创建root并渲染App组件
            const root = ReactDOM.createRoot(document.querySelector("#root"));
            root.render(<App />)
    
        </script>
    </body>
    
    </html>
    View Code

     

 
posted @ 2023-04-09 10:51  Yaopengfei  阅读(199)  评论(1编辑  收藏  举报