React(二):jsx事件绑定、条件渲染、列表渲染、jsx的本质、购物车案例

news/2024/7/15 19:34:20 标签: react.js, javascript, 前端, ecmascript, 前端框架

React(二)

  • 一、jsx事件绑定
    • 1.this的绑定方式
    • 2.jsx中绑定this的三种方式
    • 3.事件对象和传参
      • (1)事件对象怎么传
      • (2)其他参数怎么传?
  • 二、条件渲染
    • 1.直接if-else
    • 2.三元表达式
    • 3.利用逻辑中断
    • 4.案例练习
    • 5.模拟v-show的实现
  • 三、列表渲染
  • 四、jsx的本质
    • 1.babel如何转换
    • 2.虚拟DOM的生成
  • 五、购物车案例
    • 1.总价的计算
    • 2.点击加号和减号
    • 3.删除某行数据
    • 4.没有数据时给用户提示

一、jsx事件绑定

1.this的绑定方式

1、默认绑定,独立执行:fun(),一般绑定window,严格模式下undefined
2、隐式绑定:被一个对象执行:obj.fun() => obj
3、显式绑定:call / apply / bind,fun.bind('aaa) => String('aaa')
4、new绑定:new Fun(),创建一个实例,this指向实例

还是之前的一些知识点,看一下下面的代码:

javascript">const obj = {
    foo: () => {
        console.log(this);
    }
}
const obj2 = {
    onclick : obj.foo
}
obj2.onclick(); //window

这种情况我们是把obj里面的方法赋值给了另一个变量,这样我们调用的话相当于进行了默认绑定,如果上面这段不好理解,那么看看下面的:

javascript">const obj = {
    foo: () => {
        console.log(this);
    }
}
let fun = obj.foo
fun(); //window

2.jsx中绑定this的三种方式

以下面这段代码为例:

1、显式绑定this,之前我们了解的都是直接把函数的地址给到点击事件,但是由于es6中的函数默认开启严格模式,this指向undefined,需要手动利用bind改变this指向

2、利用箭头函数找到this。fun2可以以一个变量的形式保存一个箭头函数(在es6中可以省略constructor直接给变量赋值)。如果我们把一个箭头函数的地址绑定给点击事件,那么就不会有this指向问题。(箭头函数没有自己的this,会向上层作用域查找,而上层作用域是类的作用域,指向的当然是类的实例)

3、目前最常用的,直接写箭头函数,点击时触发一个箭头函数的回调,箭头函数可以调用另外一个函数,这样的话就是一个隐式绑定(this.fun3(),那么fun3里的this当然指向的也是类的实例),解决了this指向的问题。

javascript">class App extends React.Component {
    constructor() {
        super();
        this.state = {
            count: 0,
        }
    }

    fun1() {
        console.log(this);
    }

    fun2 = () => {
        console.log(this);
    }

    fun3() {
        console.log(this);
    }

    render() {
        let { count } = this.state;
        return (
            <div>
	            {/*1.显式绑定this*/}
	            <h2 onClick={this.fun1.bind(this)}>fun1</h2>
	            {/*2.利用箭头函数找到this*/}
	            <h2 onClick={this.fun2}>fun2</h2>
	            {/*3.直接写箭头函数,箭头函数调用回调*/}
	            <h2 onClick={() => console.log(this)}>fun3</h2>
	            <h2 onClick={() => this.fun3()}>fun4</h2>
            </div>
        )
    }
}
root.render(<App />);

3.事件对象和传参

(1)事件对象怎么传

让我们分别看一下这2个绑定事件的按钮:
按钮1:通过bind绑定this,这样的话会默认传一个事件对象event过去,直接就能输出
按钮2:通过箭头函数的话,需要手动传参数过去,默认传的就是event

javascript">const root = ReactDOM.createRoot(document.querySelector('#root'));
class App extends React.Component {
    btnClick(e) {
        console.log('e', e);
    }

    render() {
        return (
            <div>
                <button onClick={this.btnClick.bind(this)}>点击奥里给</button>
                <button onClick={(e) => this.btnClick(e)}>箭头函数</button>
            </div>
        )
    }
}
root.render(<App />);

(2)其他参数怎么传?

同样是两种方法,看一下下面两个按钮:
按钮1:通过bind绑定this,传入参数,这里有个大坑,那就是bind后面传的两个参数,传过去会默认放在前两个参数位置,事件对象e就会放在第三个位置,接的时候就是('zzy', 18, 事件对象),这样的话参数就会比较乱,就成了e对应zzy了,这可不行。

按钮2:使用箭头函数传递其他参数,这样的话参数不会乱,比较合适,按顺序传,按顺序接就欧了。但是这里要注意一个问题,就是这个事件对象的参数,如果箭头函数没有传e,那么就不用接收e,形参和实参一一对应就行了;而如果用上面那个按钮1,会自动传事件对象e,这个时候如果传了其他参数,会默认放在参数前几位,事件对象放最后(上面提到了)。

javascript">const root = ReactDOM.createRoot(document.querySelector('#root'));
class App extends React.Component {
    btnClick(e, name, age) {
        console.log('e', e);
        console.log('name', name);
        console.log('age', age);
    }

    render() {
        return (
            <div>
                <button onClick={this.btnClick.bind(this, 'zzy', 18)}>bind传参</button>
                <button onClick={(e) => this.btnClick(e, 'zzy', 18)}>箭头函数传参</button>
            </div>
        )
    }
}
root.render(<App />);

二、条件渲染

在vue中,我们使用v-if或v-show进行条件渲染,但是在React中,我们可以直接用原生js语法进行条件渲染,这里提出三种方式:

1.直接if-else

直接在外面定义变量,根据不同情况返回不同的dom结构

javascript">constructor() {
    super();
    this.state = {
        isLogin: false,
        friend: {
            name: 'zzy',
        }
    }
}

render() {
    let { isLogin } = this.state;
    let dom = null;
    if (isLogin) {
        dom = <div>登录成功!</div>
    } else {
        dom = <button>重新登录</button>
    }

    return (
        <div>
            {/*1.if-else条件渲染*/}
            <h2>{dom}</h2>
        </div>
    )
}

2.三元表达式

直接通过三元表达式展示不同的dom

javascript">constructor() {
    super();
    this.state = {
        isLogin: false,
    }
}

render() {
    let { isLogin} = this.state;
    return (
        <div>
            {/*2.三元运算符*/ }
            <h2>{isLogin ? '登录成功!' : <button>重新登录</button>}</h2>
        </div>
    )
}

3.利用逻辑中断

一般我们从后台获取数据时,就会使用逻辑中断,如果有数据就向后执行,没有数据就不展示。

javascript">constructor() {
    super();
    this.state = {
        friend: {
            name: 'zzy',
            description: '我很帅'
        }
    }
}

render() {
    let { friend } = this.state;

    return (
        <div>
            {/*3.逻辑中断,如果后台没有请求到数据就不执行后面的显示*/ }
            <h2>{friend && `我叫${friend.name},${friend.description}`}</h2>
        </div>
    )
}

4.案例练习

点击显示隐藏文本,用if-else实现。

javascript">constructor() {
    super();
    this.state = {
        isShow: true,
    }
}

showDom() {
    console.log(this.state.isShow)
    this.setState({
        isShow: !this.state.isShow
    })
}

render() {
    let { isShow } = this.state;
    let message = null;
    if (isShow) {
        message = 'Dj Drop the beat';
    } 

    return (
        <div>
            <button onClick={() => this.showDom()}>点击显示隐藏</button>
            <h2>{message}</h2>
        </div>
    )
}

用三元运算符或逻辑中断实现

javascript">return (
	 <div>
	     <button onClick={() => this.showDom()}>点击显示隐藏</button>
	     {/*1.用if-else实现*/}
	     <h2>{message}</h2>
	     {/*2.用三元运算符实现*/}
	     <h2>{isShow ? 'Dj drop the beat' : ''}</h2>
	     {/*3.用逻辑中断*/}
	     <h2>{isShow && 'Dj drop the beat'}</h2>
	 </div>
)

5.模拟v-show的实现

v-show主要是控制display属性,我们只要控制display属性动态变化就可以了。

<h2 style={{ display: isShow ? 'block' : 'none' }}>模拟v-show</h2>

三、列表渲染

列表渲染和vue中的v-for不同,主要用的是数组中的map这个api,同时可以结合filterslice去使用。

使用:我们展示的时候,展示数组就可以了,通过map给数组包一些标签。一般来说会包一个单独的组件。这里要注意map中这个返回标签的话,返回的东西要和return写在一行(比如这个括号),不然默认就return跳出去了(我踩的坑)

javascript">constructor() {
    super();
    this.state = {
        student: [
            { id: 1, name: 'zzy', age: 18, sex: '男' },
            { id: 2, name: 'ht', age: 16, sex: '女' },
            { id: 3, name: '张学良', age: 118, sex: '男' },
            { id: 4, name: '杨虎城', age: 18, sex: '女' },
        ]
    }
}

render() {
    let { student } = this.state;
    return (
        <div>
            {student.filter(item => item.age < 20).slice(0,2).map(item => {
                return (
                <div className='active' key={item.id}>
                    <h5>姓名:{item.name}</h5>
                    <h5>年龄:{item.age}</h5>
                    <h5>性别:{item.sex}</h5>
                </div>
                )
            })}
        </div>
    )
}

其中key的唯一标识和vue中原理差不多。

四、jsx的本质

1.babel如何转换

实际上,jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖。
所有的jsx最终都会被转换成React.createElement的函数调用。怎么理解这句话呢?

createElement需要传递三个参数:

  • 参数一:type标签
    当前ReactElement的类型;
    如果是标签元素,那么就使用字符串表示 “div”;
    如果是组件元素,那么就直接使用组件的名称;
  • 参数二:config属性
    所有jsx中的属性都在config中以对象的属性和值的形式存储
  • 参数三:children子元素
    存放在标签中的内容,以children数组的方式进行存储;
    当然,如果是多个元素呢?React内部有对它们进行处理.

所以说其实jsx就是帮助我们调用函数创建虚拟DOM,方便我们编写代码的,比如render中如果我们写下面这段代码:

<div>
    <h2>{count}</h2>
    <ul className='active'>
        <li>我是li1</li>
        <li>我是li2</li>
        <li>我是li3</li>
    </ul>
    <button>按钮</button>
</div>

经过babel转译会变成:

<script>javascript">
    /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/

    React.createElement("h2", null, count),
    
     /*#__PURE__*/React.createElement("ul", {className: "active"},
      /*#__PURE__*/React.createElement("li", null, "\u6211\u662Fli1"),
       /*#__PURE__*/React.createElement("li", null, "\u6211\u662Fli2")),

     /*#__PURE__*/React.createElement("button", null, "\u6309\u94AE"));
</script>

这样的话不用babel,我们也可以在页面展示标签,所以jsx就是个语法糖。

2.虚拟DOM的生成

上面的React.createElement 最终创建出来的是一个对象,这个对象组成一个js对象树,就是虚拟DOM
在这里插入图片描述
虚拟DOM帮助我们从命令式编程转到了声明式编程的模式,不会去频繁操作DOM,提升更新效率。

五、购物车案例

在这里插入图片描述
实现上面这个小玩意儿:

<div id="root"></div>
<script type="text/babel">javascript">
    const root = ReactDOM.createRoot(document.querySelector('#root'));
    class App extends React.Component {
        constructor() {
            super();
            this.state = {
                books: [
                    {
                        id: 1,
                        name: '《数据结构》',
                        date: '2023-2',
                        price: 56.00,
                        count: 3
                    },
                    {
                        id: 2,
                        name: '《你不知道的js》',
                        date: '2003-2',
                        price: 66.00,
                        count: 1
                    },
                    {
                        id: 3,
                        name: '《进击的巨人》',
                        date: '2013-2',
                        price: 88.00,
                        count: 1
                    },
                ],
            }
        }

        //点击加号或减号,加号传1减号传-1
        changeCount(index, count) {
            //1.对原来的数组进行浅拷贝(内部元素地址还是指向原来)
            const newBooks = [...this.state.books];
            //2.修改浅拷贝后的里面的值
            newBooks[index].count += count;
            //3.此时我们输出books会发现boosk里面对应的值也变了
            console.log(this.state.books[index].count);
            //4.最后调用setState执行render函数更新视图,把newBooks给它(React官方推荐做法)
            this.setState({
                books: newBooks,
            })
        }

        //删除某行数据
        deleteOne(index) {
            console.log(index);
            const newBooks = [...this.state.books];
            newBooks.splice(index,1);
            //操作是一样的,不同的是这里删除不会对books产生影响,因为是浅拷贝
            console.log(this.state.books);//删除某整个对象,books是不变的
            this.setState({
                books: newBooks,
            })
        }

        render() {
            let { books } = this.state;
            let totalPrice = 0;
            books.forEach(book => {
                totalPrice += book.price * book.count;
            });
            return (
                <div>
                    <table>
                        <thead>
                            <tr>
                                <th>序号</th>
                                <th>书籍名称</th>
                                <th>出版日期</th>
                                <th>价格</th>
                                <th>购买数量</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            {books.map((book,index) => {
                                return (
                                    <tr key={book.id}>
                                        <td>{book.id}</td>
                                        <td>{book.name}</td>
                                        <td>{book.date}</td>
                                        <td>{'$' + book.price}</td>
                                        <td>
                                            <button disabled={book.count == 1}
                                            onClick={() => this.changeCount(index,-1)}>-</button>
                                            {book.count}
                                            <button onClick={() => this.changeCount(index,1)}>+</button>
                                        </td>
                                        <td><button onClick={() => this.deleteOne(index)}>移除</button></td>
                                    </tr>
                                )
                            })}
                        </tbody>
                    </table>
                    <h2>总价格:{'$' + totalPrice}</h2>
                </div>
            )
        }
    }
    root.render(<App />);
</script>

总体来说逻辑并不难,主要是涉及到几个地方:
1、总价的计算
2、点击加号和减号改变该行数据
3、点击删除就去掉该行数据
4、没有数据时给用户提示

1.总价的计算

这个搞过很多次了,没什么说的,就是渲染时声明变量,然后遍历求出数量*单价的总和

javascript">let totalPrice = 0;
books.forEach(book => {
    totalPrice += book.price * book.count;
});

2.点击加号和减号

这里加减逻辑类似,所以可以封装到一个函数中,参数输了索引之外,还要传入是+1还是-1。

这里的逻辑比较有意思,因为我们不能直接去修改state中的数据,我们要借助调用setState这个方法去修改,从而可以执行render函数更新页面。

React官方推荐的做法是使用一个新的变量浅拷贝原来的数据,然后修改结束之后把新的浅拷贝赋值给state中数据

javascript">//点击加号或减号,加号传1减号传-1
changeCount(index, count) {
    //1.对原来的数组进行浅拷贝(内部元素地址还是指向原来)
    const newBooks = [...this.state.books];
    //2.修改浅拷贝后的里面的值
    newBooks[index].count += count;
    //3.此时我们输出books会发现boosk里面对应的值也变了
    console.log(this.state.books[index].count);
    //4.最后调用setState执行render函数更新视图,把newBooks给它(React官方推荐做法)
    this.setState({
        books: newBooks,
    })
}

3.删除某行数据

删除和修改的逻辑是类似的,不过修改是修改对象中某个属性,由于浅拷贝拷贝的是地址,所以修改对象中的属性,那么books和newBooks都会变。但是直接删除其中某个对象,对于浅拷贝来说,books是不会变的。

javascript">//删除某行数据
deleteOne(index) {
    console.log(index);
    const newBooks = [...this.state.books];
    newBooks.splice(index,1);
    //操作是一样的,不同的是这里删除不会对books产生影响,因为是浅拷贝
    console.log(this.state.books);//删除某整个对象,books是不变的
    this.setState({
        books: newBooks,
    })
}

4.没有数据时给用户提示

条件渲染,做法有很多,比如之前的if-else。这里我们可以搞两个函数,分别返回有数据和么有数据的DOM,然后在render函数中通过三元表达式决定调用哪个函数。总体的代码是这样的:

<div id="root"></div>
<script type="text/babel">javascript">
    const root = ReactDOM.createRoot(document.querySelector('#root'));
    class App extends React.Component {
        constructor() {
            super();
            this.state = {
                books: [......],
            }
        }
        ......

        renderData() {
            let {books} = this.state;
            let totalPrice = 0;
            books.forEach(book => {
                totalPrice += book.price * book.count;
            });
            return(
                <div>
                    <table>
                        <thead>
                            <tr>
                                <th>序号</th>
                                <th>书籍名称</th>
                                <th>出版日期</th>
                                <th>价格</th>
                                <th>购买数量</th>
                                <th>操作</th>
                            </tr>
                        </thead>
                        <tbody>
                            {books.map((book, index) => {
                                return (
                                    <tr key={book.id}>
                                        <td>{book.id}</td>
                                        <td>{book.name}</td>
                                        <td>{book.date}</td>
                                        <td>{'$' + book.price}</td>
                                        <td>
                                            <button disabled={book.count == 1}
                                                onClick={() => this.changeCount(index, -1)}>-</button>
                                            {book.count}
                                            <button onClick={() => this.changeCount(index, 1)}>+</button>
                                        </td>
                                        <td><button onClick={() => this.deleteOne(index)}>移除</button></td>
                                    </tr>
                                )
                            })}
                        </tbody>
                    </table>
                    <h2>总价格:{'$' + totalPrice}</h2>
                </div>
            )
        }

        renderNoData() {
            return <h2>没有数据,去买点东西吧tmd</h2>
        }
        render() {
            let { books } = this.state;
            return books.length ? this.renderData() : this.renderNoData();
        }
    }
    root.render(<App />);
</script>

http://www.niftyadmin.cn/n/104140.html

相关文章

动手学深度学习(第二版)学习笔记 第三章

第三章 线性神经网络 代码&#xff1a;d2l-zh/pytorch/chapter_linear-networks 3.1 线性回归 3.1. 线性回归 — 动手学深度学习 2.0.0 documentation 解析解 线性回归的解可以用一个公式简单地表达出来&#xff0c;这类解叫作解析解&#xff08;analytical solution&…

一文解读电压放大器(电压放大器原理)

关于电压放大器的科普知识&#xff0c;之前讲过很多&#xff0c;今天为大家汇总一篇文章来详细的讲解电压放大器&#xff0c;希望大家对于电压放大器能有更清晰的认识。电压放大器是什么&#xff1a;电压放大器是一种常用的电子器件&#xff0c;它的主要作用是把输入信号的振幅…

web前端性能优化

3.性能检测 当面对具体的项目实践时&#xff0c;该如何快速提升性能体验呢&#xff1f;或者说如何能够准确地定位到性能瓶颈呢&#xff1f;难道要比对着优化知识点清单&#xff0c;一项一项手动排查或完全凭借经验去处理吗&#xff1f;不&#xff0c;我们需要有一整套清晰科学…

判断一个用字符串表达的数字是否可以被整除

一.问题引出 当一个数字很大的时候,我们常用字符串进行表达,(超过了int和long等数据类型可以存储的最大范围),但是这个时候我们该如何判断他是否可以被另一个数整除呢? 这个时候我们不妨这样来考虑问题,每次将前边求模之后的数保存下来,然后乘以10和这一位的数字进行相加的操…

MATLAB R2022b 安装教程

MATLAB R2022b 安装教程MathWorks 于2022年9月发布了 MATLAB 和 Simulink 产品系列的最新版本 Matlab R2022b版本 &#xff0c;加入两个新产品&#xff1a; Medical Imaging Toolbox — 可视化、配准、分割和标注二维及三维医学图像Simscape Battery — 设计和仿真电池和储能系…

七大排序(Java)

目录 一、插入排序 1. 直接插入排序 2. 希尔排序 二、选择排序 1. 直接选择排序 2. 堆排序 三、交换排序 1. 冒泡排序 2. 快速排序 四、归并排序 五、总结 一、插入排序 1. 直接插入排序 抓一张牌&#xff0c;在有序的牌中&#xff0c;找到合适的位置并且插入。 时间…

Linux和Windows环境下配置Redis开机自启动

Linux和Windows环境下配置Redis开机自启动前言Linux服务器上设置开机自启动前置条件配置开机自启动启动的配置文件添加脚本的设置Windows设置开机自启其他简单命令前言 rt&#xff0c;没怎么接触过服务器还要摊上这么档子事&#xff0c;面试的时候也没说要跟服务器打交道啊。。…

2023年全国最新保安员精选真题及答案8

百分百题库提供保安员考试试题、保安职业资格考试预测题、保安员考试真题、保安职业资格证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 81.以下各组情形都属于区域巡逻中异常情况的是&#xff08;&#xff09;。 A&#x…