likes
comments
collection
share

React基础 - JSX语法

作者站长头像
站长
· 阅读数 53

一、认识JSX语法

1.1 认识JSX

  • 看一段代码

    // 定义跟组件
    const element = <div>Hello World</div>
    
    // 渲染跟组件
    const root = ReactDOM.createRoot(document.querySelector("#root"))
    root.render(element)
    
  • element变量的声明右侧赋值的标签语法是什么呢?

    • 不是一段字符串(因为没有使用引号包裹);

    • 它看起来是一段HTML元素,但是我们能在js中直接给一个变量赋值html吗?

    • 当然不可以,如果我们将 script标签的 type="text/babel" 去除掉,那么就会出现语法错误

    • <div>Hello World</div>,其实就是一段jsx的语法

  • JSX是什么?

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

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

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

1.2 为什么React选择JSX编写?

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

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

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

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

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

二、JSX的基本使用

2.1 JSX的书写规范

  • JSX的顶层只能有一个根元素,所以很多时候会在外层包裹一个div元素(或者Fragment)

  • 为了方便阅读,通常会在jsx的外层包裹一个小括号(),将整个jsx当作一个整体,这样jsx可以进行换行书写

  • JSX中的标签可以是单标签,也可以是双标签

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

2.2 JSX的注释写法

  • .jsx 文件中可以 使用快捷键 Ctrl + /
{ /* JSX的注释写法 */ }
{/* 我是注释 */}

2.3 JSX嵌入变量作为子元素

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

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

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

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

  • Object对象类型不能作为子元素(not valid as a React child)

    const root = ReactDOM.createRoot(document.querySelector("#root"))
    
    class App extends React.Component {
      constructor() {
        super()
    
        this.state = {
          counter: 100,
          message: "hello react",
          names: ["abc", "cba", "nba"],
    
          aaa: null,
          bbb: undefined,
          ccc: true,
          
          friend: { name: "星儿" }
        }
      }
    
      render() {
        const { counter, message, names } = this.state
        const { aaa, bbb, ccc } = this.state
        const { friend } = this.state
    
        return (
          <div>
            {/* Number/String/Array 直接显示出来 */}
            <h2>{counter}</h2>
            <h2>{message}</h2>
            <h2>{names}</h2>
    
            {/* Null/Undefined/Boolean 需要转换成字符串才能正确显示 */}
            <h2>{String(aaa)}</h2>
            <h2>{bbb + ""}</h2>
            <h2>{ccc.toString()}</h2>
            
            {/* 3.Object类型不能作为子元素进行显示 */}
            <h2>{friend.name}</h2>
            <h2>{Object.keys(friend)[0]}</h2>
          </div>
        )
      }
    }
    
    root.render(<App/>)
    

    React基础 - JSX语法

2.4 插入表达式

  • 运算表达式
  • 三元运算符
  • 执行一个函数
// 1.定义App根组件
class App extends React.Component {
  constructor() {
    super()
    this.state = {
      firstName: "kobe",
      lastName: "bryant",

      age: 20,

      movies: ["流浪地球", "星际穿越", "独行月球"]
    }
  }

  render() {
    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>)

    return (
      <div>
        <h2>{firstName + " " + lastName}</h2>
        <h2>{fullName}</h2>

        <h2>{ageText}</h2>
        <h2>{age > 18 ? "成年人" : "未成年人"}</h2>

        <ul>
          {liEls}
        </ul>

        <ul>{this.state.movies.map(movie => <li>{movie}</li>)}</ul>
        <ul>{this.getMovieEls()}</ul>
      </div>
    )
  }

  // 在逻辑比较复杂的时候,推荐将其写入方法中
  getMovieEls() {
    return this.state.movies.map(movie => <li>{movie}</li>)
  }
}

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

2.5 JSX绑定属性

  • 基本属性

  • class属性

    • 在JSX中使用className替代class
    • 绑定写法
      • 字符串拼接
      • 将所有的 class 放入数组中
      • 使用第三方库,classNames(后续补充)
  • style属性

    • 绑定对象类型
    • 注意:此处并非上述的插入变量作为子元素,所以能写入对象
    // 1.定义App根组件
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          title: "我是标题",
          imgUrl: "https://img1.baidu.com/it/u=3545225878,2086373205&fm=253&fmt=auto&app=138&f=JPEG?w=450&h=236",
          href: "https://www.baidu.com",
    
          isActive: true,
    
          objStyle: {
            color: "red",
            fontSize: "30px"
          }
        }
      }
    
      render() {
        const { title, imgUrl, href, isActive, objStyle } = this.state
    
        const classList = ["abc cba"]
        if(isActive) classList.push("active")
    
        return (
          <div>
            {/* 基本属性 */}
            <h2 title={title}>我是标题</h2>
            <img src={imgUrl} />
            <a href={href}>百度一下</a>
    
            {/* 绑定class:最好使用className */}
            <h2 className={`abc cba ${isActive ? 'active' : ""}`}>哈哈哈哈</h2>
            <h2 className={classList.join()}>哈哈哈哈</h2>
    
            {/* 绑定style:绑定对象类型 */}
            <h2 style={{color: "red", fontSize: "30px"}}>呵呵呵呵</h2>
            <h2 style={objStyle}>呵呵呵呵</h2>
          </div>
        )
      }
    }
    
    // 2.创建root并且渲染App组件
    const root = ReactDOM.createRoot(document.querySelector("#root"))
    root.render(<App/>)
    
  • 效果 React基础 - JSX语法

三、React中的JSX

3.1 事件绑定

  • 原生DOM监听事件

    • 获取DOM原生,添加监听事件

    • 在HTML原生中,直接绑定onclick

  • 在JSX中的事件监听

    • 命名采用小驼峰式(camelCase),而不是纯小写

    • 需要通过 {} 传入一个事件处理函数,这个函数会在事件发生时被执行

  • this的问题

    • 不知道 this 指向,可参考我的另一篇文章一文搞懂JavaScript中this指向

    • 在 React 类组件中,绑定事件方法中 this 指向 undefined,为什么这样呢?

      • btnClick函数并不是我们主动调用的,而且当button发生改变时,React内部调用了btnClick函数
      • 类似下面的代码,在执行的时候是拿到这个函数,独立调用,此时 this 指向 window,严格模式下,指向 undefined
      const obj = {
        name: "obj",
        foo: function() {
          console.log("foo:", this)
        }
      }
      
      // obj.foo()
      
      const config = {
        onClick: obj.foo
      }
      
      const click = config.onClick
      click()
      
    • 如何解决this的问题呢?

      • bind给btnClick显示绑定this
      • 使用 ES6 class fields 语法
      • 事件监听时传入箭头函数(推荐)
    // 1.定义App根组件
    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. bind绑定 */}
            <button onClick={this.btn1Click}>按钮1</button>
    
    
            {/* 2. ES6 class fields */}
            <button onClick={this.btn2Click}>按钮2</button>
    
    
            {/* 3. 传入一个箭头函数(推荐) */}
            <button onClick={() => console.log("btn3Click")}>按钮3</button>
    
            <button onClick={() => this.btn3Click()}>按钮3</button>
    
    
            <h2>当前计数: {this.state.counter}</h2>
          </div>
        )
      }
    }
    
    // 2.创建root并且渲染App组件
    const root = ReactDOM.createRoot(document.querySelector("#root"))
    root.render(<App/>)
    
  • 事件参数传递

    • 获取event对象

      • 很多时候我们需要拿到event对象来做一些事情(比如阻止默认行为)
      • 默认情况下,event对象有被直接传入,函数就可以获取到event对象
    • 获取更多参数

      • 有更多参数时,最好的方式就是传入一个箭头函数主动执行的事件函数,并且传入相关的其他参数
    // 1.定义App根组件
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          counter: 100
        }
      }
    
      btnClick(event, name, age) {
        console.log("btn1", event);
        console.log("name, age: ", name, age);
        this.setState({
          counter: 999
        })
      }
    
      render() {
        const { counter } = this.state
    
        return (
          <div>
            <h2>{counter}</h2>
            {/* event参数的传递 */}
            <button onClick={this.btnClick.bind(this)}>按钮1</button>
            <button onClick={(event) => this.btnClick(event)}>按钮2</button>
    
            {/* 额外参数的传递 */}
            <button onClick={this.btnClick.bind(this, "马蓝星", "28")}>按钮3(不推荐)</button>
            <button onClick={(e) => this.btnClick(e, "独孤月", "30")}>按钮4</button>
          </div>
        )
      }
    }
    
    // 2.创建root并且渲染App组件
    const root = ReactDOM.createRoot(document.querySelector("#root"))
    root.render(<App/>)
    

3.2 条件渲染

  • 某些情况下,界面的内容会根据不同的情况显示不同的内容,或者决定是否渲染某部分内容

    • 在vue中,我们会通过指令来控制:比如v-if、v-show

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

  • 常见的条件渲染的方式有哪些呢?

    • 条件判断语句:适合逻辑较多的情况

    • 三元运算符:适合逻辑比较简单

    • 与运算符&&:适合如果条件成立,渲染某一个组件;如果条件不成立,什么内容也不渲染

    • v-show的效果:主要是控制display属性是否为none

    React基础 - JSX语法

class App extends React.Component {
  constructor() {
    super();
    this.state = {
      message: "Hello World",

      isReady: false,

      friend: undefined,

      isShow: true
    };
  }

  changeShow() {
    this.setState({ isShow: !this.state.isShow })
  }

  render() {
    const { isReady, friend, isShow } = this.state;

    // 1.条件判断方式一: 使用if进行条件判断
    let showElement = null;
    if (isReady) {
      showElement = <h2>我已经准备好了</h2>;
    } else {
      showElement = <h1>还在准备中ing</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>

        {/* 4.方式四: 模拟 v-show */}
        <div>
          <button onClick={() => this.changeShow()}>切换</button>
          <h2 style={{display : isShow ? "block" : "none"}}>我是通过 display 属性控制的</h2>
        </div>
      </div>
    );
  }
}

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

3.3 列表渲染

  • 在React中并没有像 Vue 模块语法中的v-for指令,而且需要通过JavaScript代码的方式组织数据,转成JSX:

    • React中的JSX正是因为和JavaScript无缝的衔接,让它可以更加的灵活
  • 如何展示列表呢?

    • 在React中,展示列表最多的方式就是使用数组的map高阶函数
  • 很多时候在展示一个数组中的数据之前,可能需要先对它进行一些处理:

    • 过滤掉一些内容:filter函数
    • 截取数组中的一部分内容:slice函数
  • 列表中的key

    • 列表渲染如果不绑定 key ,react会抛出一个警告
    • key主要的作用是为了提高diff算法时的效率
  • 案例 React基础 - JSX语法

<!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>Document</title>
  <style>
    .item {
      margin: 10px;
      padding: 0 20px;
      border: 1px solid orange;
    }
  </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">
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          students: [
            { id: 111, name: "独孤月", score: 88 },
            { id: 112, name: "马蓝星", score: 97 },
            { id: 113, name: "朱皮特", score: 82 },
            { id: 114, name: "葫芦丝儿", score: 66 },
          ]
        }
      }

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

        // 分数大于80的学生进行展示
        const filterStudents = students.filter(item => {
          return item.score > 80
        })

        // 取两名学生
        // slice(start, end): [start, end)
        const sliceStudents = filterStudents.slice(0, 2)

        return (
          <div>
            <h2>学生列表数据</h2>
            <div className="list">
              {
                students.filter(item => item.score > 80).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>
          </div>
        )
      }
    }

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

</body>
</html>

四、JSX的本质和原理

4.1 JSX的本质

  • jsx 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖

    • 所有的 jsx 最终都会被转换成React.createElement的函数调用
  • createElement需要传递三个参数:

    • 参数一:type

      • 当前ReactElement的类型
      • 如果是标签元素,那么就使用字符串表示,比如 “div”
      • 如果是组件元素,那么就直接使用组件的名称
    • 参数二:config

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

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

4.2 JSX的原理

  • jsx的代码
    <div>
      <div className="header">Header</div>
      <div className="content">
        <div>Banner</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>
    
  • 使用babel进行转换
    • Babel官网
    • 将jsx代码放入 Babel 官网进行转换
  • 转换后的代码就可以不引入Babel依赖,type="text/babel"也可以不写
<!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>Document</title>
  </head>
  <body>
    <div id="root"></div>

    <script src="../lib/react.js"></script>
    <script src="../lib/react-dom.js"></script>

    <script>
      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, message),
              /*#__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"
            )
          );

          return element;
        }
      }

      const root = ReactDOM.createRoot(document.querySelector("#root"));
      root.render(React.createElement(App, null));
    </script>
  </body>
</html>

4.3 JSX -> 虚拟DOM -> 真实DOM

  • 在控制台打印下,看一下上面 render 函数中的 element 究竟是什么?
    • 通过 React.createElement 最终创建出来一个 ReactElement对象
    • 这个 JavaScript 对象树就是虚拟DOM React基础 - JSX语法
  • jsx代码通过 babel 转换成 ReactElement对象 通过渲染 变成真实 DOM

4.4 声明式编程

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

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

    • 在这个理念中,UI以一种理想化或者说虚拟化的方式保存在内存中,并且它是一个相对简单的JavaScript对象

    • 我们可以通过ReactDOM.render让 虚拟DOM 和 真实DOM同步起来,这个过程中叫做协调(Reconciliation)

  • 这种编程的方式赋予了React声明式的API:

    • 只需要告诉React希望让UI是什么状态
    • React来确保DOM和这些状态是匹配的
    • 不需要直接进行DOM操作,就可以从手动更改DOM、属性操作、事件处理中解放出来

五、案例练习

5.1 电影列表点击

  • 功能:
    • 电影列表,实现鼠标点击某一项,某一项变成红色
    React基础 - JSX语法
  • 初版代码
<!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>Document</title>
  <style>
    .active {
      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.定义App根组件
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          movies: ["火星救援", "飞驰人生", "独行月球", "流浪地球"],
          curIndex: 0
        }
      }

      itemClick(index) {
        this.setState({
          curIndex: index
        })
      }

      render() {
        const { movies, curIndex } = this.state

        return (
          <div>
            <ul>
              {movies.map((movie, index) => {
                return (
                  <li 
                    key={movie}
                    className={curIndex === index ? 'active' : ''}
                    onClick={() => this.itemClick(index)}
                  >
                    {movie}
                  </li>
                )
              })}
            </ul>
          </div>
        )
      }
    }

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

</body>
</html>
  • 重构一:将对 <li> 的处理抽离
    render() {
      const { movies, curIndex } = this.state
    
      const liEls = movies.map((movie, index) => {
        return (
          <li 
            key={movie}
            className={curIndex === index ? 'active' : ''}
            onClick={() => this.itemClick(index)}
          >
            {movie}
          </li>
        )
      })
    
      return (
        <div>
          <ul>{liEls}</ul>
        </div>
      )
    }
    
  • 重构二:对 map函数处理的 抽离
    render() {
      const { movies, curIndex } = this.state
    
      const movieHandle = ((movie, index) => {
        return (
          <li 
            key={movie}
            className={curIndex === index ? 'active' : ''}
            onClick={() => this.itemClick(index)}
          >
            {movie}
          </li>
        )
      })
    
      return (
        <div>
          <ul>{movies.map(movieHandle)}</ul>
        </div>
      )
    }
    

5.2 购物车

  • 效果 React基础 - JSX语法
  • 数据
    // data.js
    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
      },
      {
        id: 3,
        name: '《编程珠玑》',
        date: '2008-10',
        price: 39.00,
        count: 1
      },
      {
        id: 4,
        name: '《代码大全》',
        date: '2006-3',
        price: 128.00,
        count: 1
      },
    ]
    
  • 价格格式化
    // format.js
    function formatPrice(price) {
      return "¥" + Number(price).toFixed(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>Document</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">
        class App extends React.Component {
          constructor() {
            super()
            this.state = {
              books: books
            }
          }
    
          // 加
          increment(index) {
            // 注意:此处不能直接修改state中的数据
            const newBooks = [...this.state.books]
            newBooks[index].count += 1
            this.setState({
              books: newBooks
            })
          }
    
          // 减
          decrement(index) {
            // 注意:此处不能直接修改state中的数据
            const newBooks = [...this.state.books]
            newBooks[index].count -= 1
            this.setState({
              books: newBooks
            })
          }
    
          // 移除某一行
          removeBook(index) {
            const newBooks = [...this.state.books]
            newBooks.splice(index, 1)
            this.setState({
              books: newBooks
            })
          }
    
          // 总价格
          getTotalPrice() {
            return this.state.books.reduce((prev, item) => {
              return prev + item.price * item.count
            }, 0)
          }
    
          render() {
            const { books } = this.state
    
            let showElement = null
    
            if(books.length) {
              showElement = (
                <div>
                  <table>
                    <thead>
                      <tr>
                        <td>序号</td>
                        <td>书籍名称</td>
                        <td>出版日期</td>
                        <td>价格</td>
                        <td>购买数量</td>
                        <td>操作</td>
                      </tr>
                    </thead>
                    <tbody>
                      {books.map((book, index) => {
                        return (
                          <tr key={book.id}>
                            <td>{index + 1}</td>
                            <td>{book.name}</td>
                            <td>{book.date}</td>
                            <td>{formatPrice(book.price)}</td>
                            <td>
                              <button disabled={book.count <= 1} onClick={() => this.decrement(index)}>-</button>
                              {book.count}
                              <button onClick={() => this.increment(index)}>+</button>
                            </td>
                            <td>
                              <button onClick={() => this.removeBook(index)}>移除</button>
                            </td>
                          </tr>
                        )
                      })}  
                    </tbody>
                  </table>
                  <h2>总价格:{formatPrice(this.getTotalPrice())}</h2>
                </div>
              )
            } else {
              showElement = (
                <div>
                  <h2>购物车为空,请添加书籍~</h2>
                </div>
              )
            }
    
            return showElement
          }
        }
    
        const root = ReactDOM.createRoot(document.querySelector("#root"))
        root.render(<App/>)
      </script>
    
    </body>
    </html>
    
  • 代码优化
    • 优化控制数量的方法
    • 将书籍列表和空数据的jsx抽离为函数
    class App extends React.Component {
      constructor() {
        super()
        this.state = {
          books: books
        }
      }
    
      // 数量改变
      changeCount(index, count=1) {
        // 注意:此处不能直接修改state中的数据
        const newBooks = [...this.state.books]
        newBooks[index].count += count
        this.setState({
          books: newBooks
        })
      }
    
      // 移除某一行
      removeBook(index) {
        const newBooks = [...this.state.books]
        newBooks.splice(index, 1)
        this.setState({
          books: newBooks
        })
      }
    
      // 总价格
      getTotalPrice() {
        return this.state.books.reduce((prev, item) => {
          return prev + item.price * item.count
        }, 0)
      }
    
      // 渲染书籍列表
      renderBookList() {
        const { books } = this.state
    
        return (
          <div>
            <table>
              <thead>
                <tr>
                  <td>序号</td>
                  <td>书籍名称</td>
                  <td>出版日期</td>
                  <td>价格</td>
                  <td>购买数量</td>
                  <td>操作</td>
                </tr>
              </thead>
              <tbody>
                {books.map((book, index) => {
                  return (
                    <tr key={book.id}>
                      <td>{index + 1}</td>
                      <td>{book.name}</td>
                      <td>{book.date}</td>
                      <td>{formatPrice(book.price)}</td>
                      <td>
                        <button disabled={book.count <= 1} onClick={() => this.changeCount(index, -1)}>-</button>
                        {book.count}
                        <button onClick={() => this.changeCount(index)}>+</button>
                      </td>
                      <td>
                        <button onClick={() => this.removeBook(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()
      }
    }
    
    const root = ReactDOM.createRoot(document.querySelector("#root"))
    root.render(<App/>)
    
转载自:https://juejin.cn/post/7142366426299367460
评论
请登录