React(二) JSX中的this绑定问题;事件对象参数传递;条件渲染;列表渲染;JSX本质;购物车案例

文章目录

  • 一、jsx事件绑定
    • [1. 回顾this的绑定方式](#1. 回顾this的绑定方式)
    • [2. jsx中的this绑定问题](#2. jsx中的this绑定问题)
      • [(1) 方式一:bind绑定](#(1) 方式一:bind绑定)
      • [(2) 方式二:使用 ES6 class fields 语法](#(2) 方式二:使用 ES6 class fields 语法)
      • [(3) 方式三:直接传入一个箭头函数(重要)](#(3) 方式三:直接传入一个箭头函数(重要))
    • 3.事件参数传递
      • [(1) 传递事件对象event](#(1) 传递事件对象event)
      • [(2) 传递其他参数](#(2) 传递其他参数)
    • [4. 事件绑定小案例](#4. 事件绑定小案例)
  • 二、条件渲染
    • [1. if-else、三元运算符,逻辑与&&](#1. if-else、三元运算符,逻辑与&&)
    • [2. 模拟v-show的效果](#2. 模拟v-show的效果)
  • 三、列表渲染
  • 四、JSX的本质
    • [1. babel转译JSX](#1. babel转译JSX)
    • [2. 虚拟DOM到真实DOM](#2. 虚拟DOM到真实DOM)
  • 五、购物车案例
    • [1. 总价格与格式化](#1. 总价格与格式化)
    • [2. 商品数量+和-的操作](#2. 商品数量+和-的操作)
    • [3. 删除](#3. 删除)
    • [4. 无数据时给提示](#4. 无数据时给提示)

一、jsx事件绑定

1. 回顾this的绑定方式

(绑定方式就是指this值的几种指向)

  • 默认绑定。 函数独立执行 fun()---此时this指向window
  • 隐式绑定。被一个对象调用指向 obj.foo()---此时this指向obj
  • 显示绑定。通过call/apply/bind明确规定this的指向 --- foo.call('aaa')
  • new绑定。 new Foo()---> 创建一个实例,this指向实例

仔细看下边这段代码,分析两次函数调用分别打印什么值

javascript 复制代码
 const obj = {
   name: "obj",
   foo: function () {
     console.log("foo:", this)
   }
 }
 obj.foo() // obj

 const config = {
   onClick: obj.foo
 }
 const click = config.onClick //这句话实际是click = obj.foo
 click()  // 等价于foo(), 独立执行,指向window(严格模式值为undefined)

若修改为

javascript 复制代码
 const config = {
   onClick: obj.foo.bind(obj)
 }
 const click = config.onClick //这句话实际是click = obj.foo
 click()  // 等价于obj.foo(), 打印出来的this指向obj

2. jsx中的this绑定问题

如之前所说,在这个类组件中,事件的处理函数需要对数据进行处理;

在严格模式下,构造函数与render里的this指向实例对象,而类里的方法中的this值为undefinedundefined.setState会报错,所以需要处理this绑定问题

(1) 方式一:bind绑定

javascript 复制代码
   constructor() {
     super()
     this.state = {
       number: 0
     }
     // bind改变btnClick1 方法里this的指向
     this.btnClick1 = this.btnClick1.bind(this)
   }
   
   btnClick1 () {
     console.log('btn1', this);
     this.setState({
       number: this.state.number + 1
     })
   }
   render(){
    ...
     <button onClick={this.btnClick1}>+1</button>
    ...
   }

(2) 方式二:使用 ES6 class fields 语法

es6中,在类里可以省略constructor,直接给变量赋值,这就是class fileds。

javascript 复制代码
 class App extends React.Component {
    ...
   // 给变量btn2Click 赋值一个箭头函数
   btn2Click = () => {
     console.log("btn2Click", this)
     this.setState({ number: 1000 })
   }
   render () {
     return (
       <div>
         {/* 2.this绑定方式二: ES6 class fields */}
         <button onClick={this.btn2Click}>按钮2</button>
       </div>
     )
   }
 }

当调用箭头函数时,箭头函数没有自己的this,会向上层作用域查找,而上层作用域是类的作用域(App类),指向的则会是类的实例。

(3) 方式三:直接传入一个箭头函数(重要)

javascript 复制代码
 class App extends React.Component {
    ...
   // 给变量btn2Click 赋值一个箭头函数
   btn3Click = () => {
   // 隐式绑定后,这里的this就是render函数里的this,就是当前对象实例
     console.log("btn3Click", this) 
     this.setState({ number: 666})
   }
   render () {
     return (
       <div>
         {/* 3.this绑定方式三: 直接传入一个箭头函数 */}
          <button onClick={() => this.btn3Click()}>按钮3</button>
       </div>
     )
   }
 }

当点击按钮时,onClick被触发,执行箭头函数。而在代码逻辑在btn3Click里,所以箭头函数调用了btn3Click,而在调用btn3Click时,是this在调用,也就是一个对象调用了btn3Click,即进行了隐式绑定。

3.事件参数传递

(1) 传递事件对象event

javascript 复制代码
   btnClick (event) {
     console.log("btnClick:", event, this)
   }
   render () {
     return (
       <div>
         {/*默认参数传递*/}
         <button onClick={this.btnClick}>按钮1</button>
         <button onClick={this.btnClick.bind(this)}>按钮2</button>
         <button onClick={() => this.btnClick()}>按钮3</button>
         <button onClick={(event) => this.btnClick(event)}>按钮4</button>
       </div>
     )
   }

点击按钮1:默认传递了事件对象event,this是undefined

点击按钮2:默认传递了事件对象event,this是组件实例

点击按钮3:调用了btnClick函数,什么参数也没传。所以event是undefined;this是组件实例。

点击按钮4:传递了事件对象,所以event和this都有值

(2) 传递其他参数

在传递了事件对象的基础上,再传递其他参数

javascript 复制代码
btnClick (event, name, age) {
	console.log("btnClick:", event, this)
	console.log("name,age:", name, age)
}
...
<button onClick={(event) => this.btnClick(event, 'tom', 18)}>按钮5</button>

4. 事件绑定小案例

二、条件渲染

vue中是通过指令来控制:比如v-if、v-show;

在React中,所有的条件判断都和普通的JavaScript代码一致

1. if-else、三元运算符,逻辑与&&

  • if-else:条件判断语句,适合逻辑较多的情况
  • 三元运算符:适合逻辑简单的情况
  • 与运算符&&:适合于条件成立,则渲染组件;条件不成立,则什么也不渲染
javascript 复制代码
 this.state = {
   isReady: true,
   friend: {
     name: 'tom',
     desc: '很棒!'
   }
 }
render () {
  const { isReady, friend } = this.state
  let showEle = null
    // 采用if判断是否生成页面内容
  if (isReady) {
    showEle = <h2>准备开始比赛! </h2>
  } else {
    showEle = <h2>请提前做好准备</h2>
  }

  return (
    <div>
      {/* 1.方式一: 根据条件给变量赋值不同的内容*/}
      {showEle}
      {/* 2.方式二: 三元运算符*/}
      {isReady ? <button>点击开始战斗</button> : <h2>赶紧准备!</h2>}
      {/* 3.方式三: &&逻辑运算,friend为空则不显示,有值则显示*/}
      {friend && <h2>{friend.name + ':' + friend.desc}</h2>}
    </div>
  )
}

2. 模拟v-show的效果

控制display属性是否为none;

需求:点击按钮,实现文字的隐藏与显示

javascript 复制代码
  btnClick () {
    this.setState({
      isShow: !this.state.isShow
    })
  }
  render () {
    const { msg, isShow } = this.state
    return (
      <div>
        <button onClick={() => this.btnClick()}>切换</button>
         {/*逻辑与&&*/}
        {isShow && <h2>{msg}</h2>}
        {/*v-show的效果*/}
        <h2 style={{ display: isShow ? 'block' : 'none' }}>{msg}</h2>
      </div >
    )
  }

采用了两种实现方式,加深一下对逻辑与&&的运用

三、列表渲染

React中没有像Vue模块语法中的v-for指令,React渲染列表就是采用JS里的一些方法对列表进行遍历或其他处理。

◼ 如何展示列表呢?

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

◼ 很多时候我们在展示一个数组中的数据之前,需要先对它进行一些处理:

 比如过滤掉一些内容:filter函数

 比如截取数组中的一部分内容:slice函数

注意:这里要注意map中这个返回标签的话,返回的东西要和return写在一行(比如这个括号),不然默认就return跳出去了,什么也不会渲染。(其他博主踩的坑)

案例:展示学生信息。

javascript 复制代码
// 数据
   students: [
     { id: 111, name: "why", score: 199 },
     { id: 112, name: "kobe", score: 98 },
     { id: 113, name: "james", score: 199 },
     { id: 114, name: "curry", score: 188 },
   ]

map函数展示

javascript 复制代码
  render () {
    const { students } = this.state
    return (
      <div>
        {
          students.map((item) => {
            return (
              <div className="item" key={item.id}>
                <h2>学号: {item.id}</h2>
                <h3>姓名: {item.name}</h3>
                <h1>分数: {item.score}</h1>
              </div>
            )
          })
        }
      </div>
    )
  }

展示分数大于100的学生:filter

javascript 复制代码
render () {
   ...
  const filterStus = students.filter((item) => item.score > 100)
  return (
   ...
    filterStus.map((item) => {
      return (
        <div className="item" key={item.id}>
         ...
        </div>
      )
    })
    ...
  )
}

展示分数大于100的前两位学生:slice

javascript 复制代码
  return (
    <div>
      {
        filterStus.slice(0, 2).map((item) => {
          return (
            <div className="item" key={item.id}>
              <h2>学号: {item.id}</h2>
              <h3>姓名: {item.name}</h3>
              <h1>分数: {item.score}</h1>
            </div>
          )
        })
      }
    </div>
  )

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

四、JSX的本质

Jsx的本质是 React.createElement(type, config, ...children) 函数的语法糖。所有的jsx最终都会被转换成React.createElement的函数调用。

先看这个函数的参数:

  • 参数一:type
    当前ReactElement的类型;
    如果是标签元素,那么就使用字符串表示 "div";
    如果是组件元素,那么就直接使用组件的名称;
  • 参数二:config
    所有jsx中的属性都在config中以对象的属性和值的形式存储;
    比如传入className作为元素的class;
  • 参数三:children
    存放在标签中的内容,以children数组的方式进行存储;

1. babel转译JSX

babel将JSX语句转换成React.createElement的函数,这个函数会创建element,element最终会形成元素树。每个元素就是虚拟DOM。

比如render函数里有这样一段JSX代码

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

经babel转译,得到原生的React:

html 复制代码
<script>
    /*#__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>

(因为版本问题,我自己没有转换成这样的原生React,原因见博客:jsx的转换)

不用babel的话,需要写原生的React(React.createElement)来展示页面内容;所以JSX能够方便开发,是个语法糖,其本质就是在调用React.createElement

2. 虚拟DOM到真实DOM

(1) 、虚拟DOM的创建

React.createElement最终创建出来一个个ReactElement对象,这些对象则组成了一个JavaScript的对象树,而这个对象树就是虚拟DOM

查看ReactElement树结构(这就是虚拟DOM):

(2) 、虚拟DOM到真实DOM

流程是:

编写JSX代码

---------->经过babel转译,转成React.createElement函数的调用

--------->该函数调用生成ReactElement对象,形成虚拟DOM

--------->虚拟DOM经过渲染生成真实DOM,这个渲染的过程就是React做的。

(3) 、虚拟DOM的作用

主要的作用:

  • 当更新数据时,不用将所有DOM重新渲染;新旧虚拟DOM对比,来快速决定哪些东西更新,哪些东西不用更新。

  • 做跨平台应用程序。虚拟DOM的本质就是JS对象,下一步是要渲染到页面上,

    • React可以将其渲染到Web应用的界面上(就是调用原生的document.createElement),将按钮渲染成网页的按钮button等。
    • 也可以将其渲染到IOS/Android端,渲染成IOS/Android控件,将按钮渲染成移动端的UIButton控件。
  • 虚拟DOM帮助我们从命令式编程转到了声明式编程的模式。

    你只需要告诉React希望让UI是什么状态; React来确保DOM和这些状态是匹配的;(也就是 你不需要直接进行DOM操作,不需要手动渲染。只需要写好状态(state的数据),写好页面结构即可。更改DOM,渲染的事交给React。)

五、购物车案例

实现以下这个页面及功能:

html 复制代码
  <script src="./data.js"></script>
  <div id="root"></div>
  <script type="text/babel">

    // 1. 定义类组件
    class App extends React.Component {
      // 1.1 构造函数
      constructor() {
        super()
        this.state = {
          books: books
        }
      }
      render () {
        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={index}>
                      <td>{index + 1}</td>
                      <td>{item.name}</td>
                      <td>{item.date}</td>
                      <td>{item.price}</td>
                      <td>
                        <button disabled>-</button>
                        {item.count}
                        <button>+</button>
                      </td>
                      <td><button>删除</button></td>
                    </tr>
                  )
                })}
              </tbody>
            </table>
            <h2>总价格:¥311</h2>
          </div>
        )
      }
    }

    // 2. 渲染类组件
    const root = ReactDOM.createRoot(document.querySelector('#root'))
    root.render(<App />)

  </script>

data.js里的数据为

javascript 复制代码
const books = [
  {
    id: 1,
    name: '《算法导论》',
    date: '2006-9',
    price: 85.00,
    count: 1
  },
  {
    id: 2,
    name: '《UNIX编程艺术》',
    date: '2006-2',
    price: 59.00,
    count: 1
  }
  ...
]

1. 总价格与格式化

(1)计算总价格 :每本书的单价*数量,相加

方式一:for循环遍历计算

方式二:reduce函数

javascript 复制代码
// 封装为函数
 getTotalPrice () {
   let totalPrice = this.state.books.reduce((preValue, current) => {
     return preValue + current.count * current.price
   }, 0)
   return totalPrice
 }

(2)格式化

表格里显示价格的地方,格式都是¥xxx.xx,因此可写个函数格式化

javascript 复制代码
function formatPrice (price) {
  // toFixed,保留两位小数
  return '¥' + Number(price).toFixed(2)
}
// 应用:
//表格中价格一列
<td>{formatPrice(item.price)}</td>
//总价格
<h2>总价格:{formatPrice(this.getTotalPrice())}</h2>

2. 商品数量+和-的操作

javascript 复制代码
  // 加
  increment (index) {
    const newBooks = [...this.state.books]
    newBooks[index].count += 1
    this.setState({ books: newBooks })
  }
  // 减
  decrement (index) {
    const newBooks = [...this.state.books]
    newBooks[index].count += -1
    this.setState({ books: newBooks })
  }

可以看出,这两个操作十分相似,唯一的区别是对count的操作数不一样。所以可以进一步封装。

我们不能直接去修改state中的数据,我们要借助调用setState这个方法去修改,从而可以执行render函数更新页面。

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

javascript 复制代码
 // 商品数量变化,count为+1或-1
 changeCount (index, count) {
   const newBooks = [...this.state.books]
   newBooks[index].count += count
   this.setState({
     books: newBooks
   })
 }

需要注意当数量小于或等于1时,减号按钮应该禁用。

3. 删除

数组中删除数据,用到splice函数

javascript 复制代码
  // 处理删除
  removeItem (index) {
    const newBooks = [...this.state.books]
    // 删除数据
    newBooks.splice(index, 1)
    this.setState({ books: newBooks })
  }
 // 页面
<td><button onClick={() => this.removeItem(index)}>删除</button></td>

4. 无数据时给提示

考虑到条件渲染,当有数据时展示表格,无数据时隐藏表格。可将有数据与无数据时的页面结构封装到不同的函数里。

相关推荐
腾讯TNTWeb前端团队6 小时前
helux v5 发布了,像pinia一样优雅地管理你的react状态吧
前端·javascript·react.js
范文杰9 小时前
AI 时代如何更高效开发前端组件?21st.dev 给了一种答案
前端·ai编程
拉不动的猪9 小时前
刷刷题50(常见的js数据通信与渲染问题)
前端·javascript·面试
拉不动的猪9 小时前
JS多线程Webworks中的几种实战场景演示
前端·javascript·面试
FreeCultureBoy10 小时前
macOS 命令行 原生挂载 webdav 方法
前端
uhakadotcom11 小时前
Astro 框架:快速构建内容驱动型网站的利器
前端·javascript·面试
uhakadotcom11 小时前
了解Nest.js和Next.js:如何选择合适的框架
前端·javascript·面试
uhakadotcom11 小时前
React与Next.js:基础知识及应用场景
前端·面试·github
uhakadotcom11 小时前
Remix 框架:性能与易用性的完美结合
前端·javascript·面试
uhakadotcom11 小时前
Node.js 包管理器:npm vs pnpm
前端·javascript·面试